blob: 79be0f64e35b75cddb5126e9fb5a1ac64515b33c [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
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000019 def add_hor_rule(self, abswidth=None, percentwidth=1.0,
20 height=None, align=None): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000021 def add_label_data(self, format, counter): pass
22 def add_flowing_data(self, data): pass
23 def add_literal_data(self, data): pass
24 def flush_softspace(self): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000025 def push_alignment(self, align): pass
26 def pop_alignment(self): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000027 def push_font(self, x): pass
28 def pop_font(self): pass
29 def push_margin(self, margin): pass
30 def pop_margin(self): pass
31 def set_spacing(self, spacing): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000032 def push_style(self, *styles): pass
33 def pop_style(self, n=1): pass
34 def assert_line_data(self, flag=1): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000035
36
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000037class AbstractFormatter:
38
Guido van Rossum8e449911996-08-26 16:19:23 +000039 # Space handling policy: blank spaces at the boundary between elements
40 # are handled by the outermost context. "Literal" data is not checked
41 # to determine context, so spaces in literal data are handled directly
42 # in all circumstances.
43
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000044 def __init__(self, writer):
45 self.writer = writer # Output device
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000046 self.align = None # Current alignment
47 self.align_stack = [] # Alignment stack
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000048 self.font_stack = [] # Font state
49 self.margin_stack = [] # Margin state
50 self.spacing = None # Vertical spacing state
51 self.style_stack = [] # Other state, e.g. color
Guido van Rossum909507d1995-10-06 15:31:30 +000052 self.nospace = 1 # Should leading space be suppressed
53 self.softspace = 0 # Should a space be inserted
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000054 self.para_end = 1 # Just ended a paragraph
55 self.parskip = 0 # Skipped space between paragraphs?
56 self.hard_break = 1 # Have a hard break
57 self.have_label = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000058
59 def end_paragraph(self, blankline):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000060 if not self.hard_break:
61 self.writer.send_line_break()
62 self.have_label = 0
63 if self.parskip < blankline and not self.have_label:
64 self.writer.send_paragraph(blankline - self.parskip)
65 self.parskip = blankline
66 self.have_label = 0
67 self.hard_break = self.nospace = self.para_end = 1
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000068 self.softspace = 0
69
70 def add_line_break(self):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000071 if not (self.hard_break or self.para_end):
72 self.writer.send_line_break()
73 self.have_label = self.parskip = 0
74 self.hard_break = self.nospace = 1
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000075 self.softspace = 0
76
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000077 def add_hor_rule(self, *args, **kw):
78 if not self.hard_break:
79 self.writer.send_line_break()
80 apply(self.writer.send_hor_rule, args, kw)
81 self.hard_break = self.nospace = 1
82 self.have_label = self.para_end = self.softspace = self.parskip = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000083
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000084 def add_label_data(self, format, counter, blankline = None):
85 if self.have_label or not self.hard_break:
86 self.writer.send_line_break()
87 if not self.para_end:
88 self.writer.send_paragraph((blankline and 1) or 0)
89 if type(format) is StringType:
90 self.writer.send_label_data(self.format_counter(format, counter))
91 else:
92 self.writer.send_label_data(format)
93 self.nospace = self.have_label = self.hard_break = self.para_end = 1
94 self.softspace = self.parskip = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000095
96 def format_counter(self, format, counter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000097 label = ''
98 for c in format:
99 try:
100 if c == '1':
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000101 label = label + ('%d' % counter)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000102 elif c in 'aA':
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000103 if counter > 0:
104 label = label + self.format_letter(c, counter)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000105 elif c in 'iI':
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000106 if counter > 0:
107 label = label + self.format_roman(c, counter)
108 else:
109 label = label + c
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000110 except:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000111 label = label + c
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000112 return label
113
114 def format_letter(self, case, counter):
115 label = ''
116 while counter > 0:
117 counter, x = divmod(counter-1, 26)
118 s = chr(ord(case) + x)
119 label = s + label
120 return label
121
122 def format_roman(self, case, counter):
123 ones = ['i', 'x', 'c', 'm']
124 fives = ['v', 'l', 'd']
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000125 label, index = '', 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000126 # This will die of IndexError when counter is too big
127 while counter > 0:
128 counter, x = divmod(counter, 10)
129 if x == 9:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000130 label = ones[index] + ones[index+1] + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000131 elif x == 4:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000132 label = ones[index] + fives[index] + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000133 else:
134 if x >= 5:
135 s = fives[index]
136 x = x-5
137 else:
138 s = ''
139 s = s + ones[index]*x
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000140 label = s + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000141 index = index + 1
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000142 if case == 'I':
143 return string.upper(label)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000144 return label
145
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000146 def add_flowing_data(self, data,
147 # These are only here to load them into locals:
148 whitespace = string.whitespace,
149 join = string.join, split = string.split):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000150 if not data: return
Guido van Rossum93dc8011996-02-12 23:59:54 +0000151 # The following looks a bit convoluted but is a great improvement over
152 # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000153 prespace = data[:1] in whitespace
154 postspace = data[-1:] in whitespace
155 data = join(split(data))
156 if self.nospace and not data:
157 return
158 elif prespace or self.softspace:
159 if not data:
160 if not self.nospace:
161 self.softspace = 1
162 self.parskip = 0
163 return
164 if not self.nospace:
165 data = ' ' + data
166 self.hard_break = self.nospace = self.para_end = \
167 self.parskip = self.have_label = 0
168 self.softspace = postspace
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000169 self.writer.send_flowing_data(data)
170
171 def add_literal_data(self, data):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000172 if not data: return
Guido van Rossum8e449911996-08-26 16:19:23 +0000173 if self.softspace:
174 self.writer.send_flowing_data(" ")
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000175 self.hard_break = data[-1:] == '\n'
176 self.nospace = self.para_end = self.softspace = \
177 self.parskip = self.have_label = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000178 self.writer.send_literal_data(data)
179
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000180 def flush_softspace(self):
181 if self.softspace:
Guido van Rossum8e449911996-08-26 16:19:23 +0000182 self.hard_break = self.para_end = self.parskip = \
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000183 self.have_label = self.softspace = 0
Guido van Rossum8e449911996-08-26 16:19:23 +0000184 self.nospace = 1
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000185 self.writer.send_flowing_data(' ')
186
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000187 def push_alignment(self, align):
188 if align and align != self.align:
189 self.writer.new_alignment(align)
190 self.align = align
191 self.align_stack.append(align)
192 else:
193 self.align_stack.append(self.align)
194
195 def pop_alignment(self):
196 if self.align_stack:
197 del self.align_stack[-1]
198 if self.align_stack:
199 self.align = align = self.align_stack[-1]
200 self.writer.new_alignment(align)
201 else:
202 self.align = None
203 self.writer.new_alignment(None)
204
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000205 def push_font(self, (size, i, b, tt)):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000206 if self.softspace:
Guido van Rossum8e449911996-08-26 16:19:23 +0000207 self.hard_break = self.para_end = self.softspace = 0
208 self.nospace = 1
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000209 self.writer.send_flowing_data(' ')
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000210 if self.font_stack:
211 csize, ci, cb, ctt = self.font_stack[-1]
212 if size is AS_IS: size = csize
213 if i is AS_IS: i = ci
214 if b is AS_IS: b = cb
215 if tt is AS_IS: tt = ctt
216 font = (size, i, b, tt)
217 self.font_stack.append(font)
218 self.writer.new_font(font)
219
220 def pop_font(self):
221 if self.font_stack:
222 del self.font_stack[-1]
223 if self.font_stack:
224 font = self.font_stack[-1]
225 else:
226 font = None
227 self.writer.new_font(font)
228
229 def push_margin(self, margin):
230 self.margin_stack.append(margin)
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000231 fstack = filter(None, self.margin_stack)
232 if not margin and fstack:
233 margin = fstack[-1]
234 self.writer.new_margin(margin, len(fstack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000235
236 def pop_margin(self):
237 if self.margin_stack:
238 del self.margin_stack[-1]
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000239 fstack = filter(None, self.margin_stack)
240 if fstack:
241 margin = fstack[-1]
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000242 else:
243 margin = None
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000244 self.writer.new_margin(margin, len(fstack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000245
246 def set_spacing(self, spacing):
247 self.spacing = spacing
248 self.writer.new_spacing(spacing)
249
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000250 def push_style(self, *styles):
251 if self.softspace:
Guido van Rossum8e449911996-08-26 16:19:23 +0000252 self.hard_break = self.para_end = self.softspace = 0
253 self.nospace = 1
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000254 self.writer.send_flowing_data(' ')
255 for style in styles:
256 self.style_stack.append(style)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000257 self.writer.new_styles(tuple(self.style_stack))
258
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000259 def pop_style(self, n=1):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000260 del self.style_stack[-n:]
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000261 self.writer.new_styles(tuple(self.style_stack))
262
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000263 def assert_line_data(self, flag=1):
264 self.nospace = self.hard_break = not flag
Guido van Rossum8e449911996-08-26 16:19:23 +0000265 self.para_end = self.parskip = self.have_label = 0
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000266
267
268class NullWriter:
269 """Minimal writer interface to use in testing.
270 """
Guido van Rossum3672aa21996-05-29 00:02:30 +0000271 def __init__(self): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000272 def new_alignment(self, align): pass
273 def new_font(self, font): pass
274 def new_margin(self, margin, level): pass
275 def new_spacing(self, spacing): pass
276 def new_styles(self, styles): pass
277 def send_paragraph(self, blankline): pass
278 def send_line_break(self): pass
279 def send_hor_rule(self, *args, **kw): pass
280 def send_label_data(self, data): pass
281 def send_flowing_data(self, data): pass
282 def send_literal_data(self, data): pass
283
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000284
Guido van Rossum3672aa21996-05-29 00:02:30 +0000285class AbstractWriter(NullWriter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000286
287 def __init__(self):
288 pass
289
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000290 def new_alignment(self, align):
291 print "new_alignment(%s)" % `align`
292
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000293 def new_font(self, font):
294 print "new_font(%s)" % `font`
295
296 def new_margin(self, margin, level):
297 print "new_margin(%s, %d)" % (`margin`, level)
298
299 def new_spacing(self, spacing):
300 print "new_spacing(%s)" % `spacing`
301
302 def new_styles(self, styles):
303 print "new_styles(%s)" % `styles`
304
305 def send_paragraph(self, blankline):
306 print "send_paragraph(%s)" % `blankline`
307
308 def send_line_break(self):
309 print "send_line_break()"
310
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000311 def send_hor_rule(self, *args, **kw):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000312 print "send_hor_rule()"
313
314 def send_label_data(self, data):
315 print "send_label_data(%s)" % `data`
316
317 def send_flowing_data(self, data):
318 print "send_flowing_data(%s)" % `data`
319
320 def send_literal_data(self, data):
321 print "send_literal_data(%s)" % `data`
322
323
Guido van Rossum3672aa21996-05-29 00:02:30 +0000324class DumbWriter(NullWriter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000325
326 def __init__(self, file=None, maxcol=72):
327 self.file = file or sys.stdout
328 self.maxcol = maxcol
Guido van Rossum3672aa21996-05-29 00:02:30 +0000329 NullWriter.__init__(self)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000330 self.reset()
331
332 def reset(self):
333 self.col = 0
334 self.atbreak = 0
335
336 def send_paragraph(self, blankline):
337 self.file.write('\n' + '\n'*blankline)
338 self.col = 0
339 self.atbreak = 0
340
341 def send_line_break(self):
342 self.file.write('\n')
343 self.col = 0
344 self.atbreak = 0
345
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000346 def send_hor_rule(self, *args, **kw):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000347 self.file.write('\n')
348 self.file.write('-'*self.maxcol)
349 self.file.write('\n')
350 self.col = 0
351 self.atbreak = 0
352
353 def send_literal_data(self, data):
354 self.file.write(data)
355 i = string.rfind(data, '\n')
356 if i >= 0:
357 self.col = 0
358 data = data[i+1:]
359 data = string.expandtabs(data)
360 self.col = self.col + len(data)
361 self.atbreak = 0
362
363 def send_flowing_data(self, data):
364 if not data: return
365 atbreak = self.atbreak or data[0] in string.whitespace
366 col = self.col
367 maxcol = self.maxcol
368 write = self.file.write
369 for word in string.split(data):
370 if atbreak:
371 if col + len(word) >= maxcol:
372 write('\n')
373 col = 0
374 else:
375 write(' ')
376 col = col + 1
377 write(word)
378 col = col + len(word)
379 atbreak = 1
380 self.col = col
381 self.atbreak = data[-1] in string.whitespace
382
383
384def test(file = None):
385 w = DumbWriter()
386 f = AbstractFormatter(w)
387 if file:
388 fp = open(file)
389 elif sys.argv[1:]:
390 fp = open(sys.argv[1])
391 else:
392 fp = sys.stdin
393 while 1:
394 line = fp.readline()
395 if not line:
396 break
397 if line == '\n':
398 f.end_paragraph(1)
399 else:
400 f.add_flowing_data(line)
401 f.end_paragraph(0)
402
403
404if __name__ == '__main__':
405 test()