blob: 026637940e5ad65761596d3313a409970b5c67dc [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
13 def __init__(self): pass
14 def end_paragraph(self, blankline): pass
15 def add_line_break(self): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000016 def add_hor_rule(self, abswidth=None, percentwidth=1.0,
17 height=None, align=None): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000018 def add_label_data(self, format, counter): pass
19 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
36 def __init__(self, writer):
37 self.writer = writer # Output device
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000038 self.align = None # Current alignment
39 self.align_stack = [] # Alignment stack
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000040 self.font_stack = [] # Font state
41 self.margin_stack = [] # Margin state
42 self.spacing = None # Vertical spacing state
43 self.style_stack = [] # Other state, e.g. color
Guido van Rossum909507d1995-10-06 15:31:30 +000044 self.nospace = 1 # Should leading space be suppressed
45 self.softspace = 0 # Should a space be inserted
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000046 self.para_end = 1 # Just ended a paragraph
47 self.parskip = 0 # Skipped space between paragraphs?
48 self.hard_break = 1 # Have a hard break
49 self.have_label = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000050
51 def end_paragraph(self, blankline):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000052 if not self.hard_break:
53 self.writer.send_line_break()
54 self.have_label = 0
55 if self.parskip < blankline and not self.have_label:
56 self.writer.send_paragraph(blankline - self.parskip)
57 self.parskip = blankline
58 self.have_label = 0
59 self.hard_break = self.nospace = self.para_end = 1
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000060 self.softspace = 0
61
62 def add_line_break(self):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000063 if not (self.hard_break or self.para_end):
64 self.writer.send_line_break()
65 self.have_label = self.parskip = 0
66 self.hard_break = self.nospace = 1
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000067 self.softspace = 0
68
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000069 def add_hor_rule(self, *args, **kw):
70 if not self.hard_break:
71 self.writer.send_line_break()
72 apply(self.writer.send_hor_rule, args, kw)
73 self.hard_break = self.nospace = 1
74 self.have_label = self.para_end = self.softspace = self.parskip = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000075
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000076 def add_label_data(self, format, counter, blankline = None):
77 if self.have_label or not self.hard_break:
78 self.writer.send_line_break()
79 if not self.para_end:
80 self.writer.send_paragraph((blankline and 1) or 0)
81 if type(format) is StringType:
82 self.writer.send_label_data(self.format_counter(format, counter))
83 else:
84 self.writer.send_label_data(format)
85 self.nospace = self.have_label = self.hard_break = self.para_end = 1
86 self.softspace = self.parskip = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000087
88 def format_counter(self, format, counter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000089 label = ''
90 for c in format:
91 try:
92 if c == '1':
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000093 label = label + ('%d' % counter)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000094 elif c in 'aA':
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000095 if counter > 0:
96 label = label + self.format_letter(c, counter)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000097 elif c in 'iI':
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000098 if counter > 0:
99 label = label + self.format_roman(c, counter)
100 else:
101 label = label + c
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000102 except:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000103 label = label + c
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000104 return label
105
106 def format_letter(self, case, counter):
107 label = ''
108 while counter > 0:
109 counter, x = divmod(counter-1, 26)
110 s = chr(ord(case) + x)
111 label = s + label
112 return label
113
114 def format_roman(self, case, counter):
115 ones = ['i', 'x', 'c', 'm']
116 fives = ['v', 'l', 'd']
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000117 label, index = '', 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000118 # This will die of IndexError when counter is too big
119 while counter > 0:
120 counter, x = divmod(counter, 10)
121 if x == 9:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000122 label = ones[index] + ones[index+1] + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000123 elif x == 4:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000124 label = ones[index] + fives[index] + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000125 else:
126 if x >= 5:
127 s = fives[index]
128 x = x-5
129 else:
130 s = ''
131 s = s + ones[index]*x
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000132 label = s + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000133 index = index + 1
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000134 if case == 'I':
135 return string.upper(label)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000136 return label
137
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000138 def add_flowing_data(self, data,
139 # These are only here to load them into locals:
140 whitespace = string.whitespace,
141 join = string.join, split = string.split):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000142 if not data: return
Guido van Rossum93dc8011996-02-12 23:59:54 +0000143 # The following looks a bit convoluted but is a great improvement over
144 # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000145 prespace = data[:1] in whitespace
146 postspace = data[-1:] in whitespace
147 data = join(split(data))
148 if self.nospace and not data:
149 return
150 elif prespace or self.softspace:
151 if not data:
152 if not self.nospace:
153 self.softspace = 1
154 self.parskip = 0
155 return
156 if not self.nospace:
157 data = ' ' + data
158 self.hard_break = self.nospace = self.para_end = \
159 self.parskip = self.have_label = 0
160 self.softspace = postspace
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000161 self.writer.send_flowing_data(data)
162
163 def add_literal_data(self, data):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000164 if not data: return
165 # Caller is expected to cause flush_softspace() if needed.
166 self.hard_break = data[-1:] == '\n'
167 self.nospace = self.para_end = self.softspace = \
168 self.parskip = self.have_label = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000169 self.writer.send_literal_data(data)
170
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000171 def flush_softspace(self):
172 if self.softspace:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000173 self.hard_break = self.nospace = self.para_end = self.parskip = \
174 self.have_label = self.softspace = 0
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000175 self.writer.send_flowing_data(' ')
176
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000177 def push_alignment(self, align):
178 if align and align != self.align:
179 self.writer.new_alignment(align)
180 self.align = align
181 self.align_stack.append(align)
182 else:
183 self.align_stack.append(self.align)
184
185 def pop_alignment(self):
186 if self.align_stack:
187 del self.align_stack[-1]
188 if self.align_stack:
189 self.align = align = self.align_stack[-1]
190 self.writer.new_alignment(align)
191 else:
192 self.align = None
193 self.writer.new_alignment(None)
194
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000195 def push_font(self, (size, i, b, tt)):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000196 if self.softspace:
197 self.hard_break = self.nospace = self.para_end = self.softspace = 0
198 self.writer.send_flowing_data(' ')
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000199 if self.font_stack:
200 csize, ci, cb, ctt = self.font_stack[-1]
201 if size is AS_IS: size = csize
202 if i is AS_IS: i = ci
203 if b is AS_IS: b = cb
204 if tt is AS_IS: tt = ctt
205 font = (size, i, b, tt)
206 self.font_stack.append(font)
207 self.writer.new_font(font)
208
209 def pop_font(self):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000210 if self.softspace:
211 self.hard_break = self.nospace = self.para_end = self.softspace = 0
212 self.writer.send_flowing_data(' ')
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000213 if self.font_stack:
214 del self.font_stack[-1]
215 if self.font_stack:
216 font = self.font_stack[-1]
217 else:
218 font = None
219 self.writer.new_font(font)
220
221 def push_margin(self, margin):
222 self.margin_stack.append(margin)
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000223 fstack = filter(None, self.margin_stack)
224 if not margin and fstack:
225 margin = fstack[-1]
226 self.writer.new_margin(margin, len(fstack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000227
228 def pop_margin(self):
229 if self.margin_stack:
230 del self.margin_stack[-1]
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000231 fstack = filter(None, self.margin_stack)
232 if fstack:
233 margin = fstack[-1]
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000234 else:
235 margin = None
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000236 self.writer.new_margin(margin, len(fstack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000237
238 def set_spacing(self, spacing):
239 self.spacing = spacing
240 self.writer.new_spacing(spacing)
241
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000242 def push_style(self, *styles):
243 if self.softspace:
244 self.hard_break = self.nospace = self.para_end = self.softspace = 0
245 self.writer.send_flowing_data(' ')
246 for style in styles:
247 self.style_stack.append(style)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000248 self.writer.new_styles(tuple(self.style_stack))
249
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000250 def pop_style(self, n=1):
251 if self.softspace:
252 self.hard_break = self.nospace = self.para_end = self.softspace = 0
253 self.writer.send_flowing_data(' ')
254 del self.style_stack[-n:]
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000255 self.writer.new_styles(tuple(self.style_stack))
256
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000257 def assert_line_data(self, flag=1):
258 self.nospace = self.hard_break = not flag
259 self.para_end = self.have_label = 0
260
261
262class NullWriter:
263 """Minimal writer interface to use in testing.
264 """
Guido van Rossum3672aa21996-05-29 00:02:30 +0000265 def __init__(self): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000266 def new_alignment(self, align): pass
267 def new_font(self, font): pass
268 def new_margin(self, margin, level): pass
269 def new_spacing(self, spacing): pass
270 def new_styles(self, styles): pass
271 def send_paragraph(self, blankline): pass
272 def send_line_break(self): pass
273 def send_hor_rule(self, *args, **kw): pass
274 def send_label_data(self, data): pass
275 def send_flowing_data(self, data): pass
276 def send_literal_data(self, data): pass
277
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000278
Guido van Rossum3672aa21996-05-29 00:02:30 +0000279class AbstractWriter(NullWriter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000280
281 def __init__(self):
282 pass
283
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000284 def new_alignment(self, align):
285 print "new_alignment(%s)" % `align`
286
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000287 def new_font(self, font):
288 print "new_font(%s)" % `font`
289
290 def new_margin(self, margin, level):
291 print "new_margin(%s, %d)" % (`margin`, level)
292
293 def new_spacing(self, spacing):
294 print "new_spacing(%s)" % `spacing`
295
296 def new_styles(self, styles):
297 print "new_styles(%s)" % `styles`
298
299 def send_paragraph(self, blankline):
300 print "send_paragraph(%s)" % `blankline`
301
302 def send_line_break(self):
303 print "send_line_break()"
304
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000305 def send_hor_rule(self, *args, **kw):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000306 print "send_hor_rule()"
307
308 def send_label_data(self, data):
309 print "send_label_data(%s)" % `data`
310
311 def send_flowing_data(self, data):
312 print "send_flowing_data(%s)" % `data`
313
314 def send_literal_data(self, data):
315 print "send_literal_data(%s)" % `data`
316
317
Guido van Rossum3672aa21996-05-29 00:02:30 +0000318class DumbWriter(NullWriter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000319
320 def __init__(self, file=None, maxcol=72):
321 self.file = file or sys.stdout
322 self.maxcol = maxcol
Guido van Rossum3672aa21996-05-29 00:02:30 +0000323 NullWriter.__init__(self)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000324 self.reset()
325
326 def reset(self):
327 self.col = 0
328 self.atbreak = 0
329
330 def send_paragraph(self, blankline):
331 self.file.write('\n' + '\n'*blankline)
332 self.col = 0
333 self.atbreak = 0
334
335 def send_line_break(self):
336 self.file.write('\n')
337 self.col = 0
338 self.atbreak = 0
339
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000340 def send_hor_rule(self, *args, **kw):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000341 self.file.write('\n')
342 self.file.write('-'*self.maxcol)
343 self.file.write('\n')
344 self.col = 0
345 self.atbreak = 0
346
347 def send_literal_data(self, data):
348 self.file.write(data)
349 i = string.rfind(data, '\n')
350 if i >= 0:
351 self.col = 0
352 data = data[i+1:]
353 data = string.expandtabs(data)
354 self.col = self.col + len(data)
355 self.atbreak = 0
356
357 def send_flowing_data(self, data):
358 if not data: return
359 atbreak = self.atbreak or data[0] in string.whitespace
360 col = self.col
361 maxcol = self.maxcol
362 write = self.file.write
363 for word in string.split(data):
364 if atbreak:
365 if col + len(word) >= maxcol:
366 write('\n')
367 col = 0
368 else:
369 write(' ')
370 col = col + 1
371 write(word)
372 col = col + len(word)
373 atbreak = 1
374 self.col = col
375 self.atbreak = data[-1] in string.whitespace
376
377
378def test(file = None):
379 w = DumbWriter()
380 f = AbstractFormatter(w)
381 if file:
382 fp = open(file)
383 elif sys.argv[1:]:
384 fp = open(sys.argv[1])
385 else:
386 fp = sys.stdin
387 while 1:
388 line = fp.readline()
389 if not line:
390 break
391 if line == '\n':
392 f.end_paragraph(1)
393 else:
394 f.add_flowing_data(line)
395 f.end_paragraph(0)
396
397
398if __name__ == '__main__':
399 test()