blob: c192e20ddb567525a77fe1af219237fb33e628ee [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 Rossum8e449911996-08-26 16:19:23 +000013 def __init__(self, writer): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000014 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
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):
42 self.writer = writer # Output device
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000043 self.align = None # Current alignment
44 self.align_stack = [] # Alignment stack
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000045 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
Guido van Rossum909507d1995-10-06 15:31:30 +000049 self.nospace = 1 # Should leading space be suppressed
50 self.softspace = 0 # Should a space be inserted
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000051 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 Rossum9787bdaf1996-05-28 23:50:49 +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
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000065 self.softspace = 0
66
67 def add_line_break(self):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +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
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000072 self.softspace = 0
73
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000074 def add_hor_rule(self, *args, **kw):
75 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):
82 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 Rossum9787bdaf1996-05-28 23:50:49 +000098 label = label + ('%d' % counter)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000099 elif c in 'aA':
Guido van Rossum9787bdaf1996-05-28 23:50:49 +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 Rossum9787bdaf1996-05-28 23:50:49 +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):
112 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
118
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 Rossuma0eab1d1995-08-07 20:07:36 +0000123 # This will die of IndexError when counter is too big
124 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 Rossum9787bdaf1996-05-28 23:50:49 +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':
140 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,
144 # These are only here to load them into locals:
145 whitespace = string.whitespace,
146 join = string.join, split = string.split):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000147 if not data: return
Guido van Rossum93dc8011996-02-12 23:59:54 +0000148 # The following looks a bit convoluted but is a great improvement over
149 # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000150 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
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000166 self.writer.send_flowing_data(data)
167
168 def add_literal_data(self, data):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000169 if not data: return
Guido van Rossum8e449911996-08-26 16:19:23 +0000170 if self.softspace:
171 self.writer.send_flowing_data(" ")
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000172 self.hard_break = data[-1:] == '\n'
173 self.nospace = self.para_end = self.softspace = \
174 self.parskip = self.have_label = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000175 self.writer.send_literal_data(data)
176
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000177 def flush_softspace(self):
178 if self.softspace:
Guido van Rossum8e449911996-08-26 16:19:23 +0000179 self.hard_break = self.para_end = self.parskip = \
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000180 self.have_label = self.softspace = 0
Guido van Rossum8e449911996-08-26 16:19:23 +0000181 self.nospace = 1
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000182 self.writer.send_flowing_data(' ')
183
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000184 def push_alignment(self, align):
185 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)
191
192 def pop_alignment(self):
193 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)
201
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000202 def push_font(self, (size, i, b, tt)):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000203 if self.softspace:
Guido van Rossum8e449911996-08-26 16:19:23 +0000204 self.hard_break = self.para_end = self.softspace = 0
205 self.nospace = 1
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000206 self.writer.send_flowing_data(' ')
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000207 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)
216
217 def pop_font(self):
218 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)
225
226 def push_margin(self, margin):
227 self.margin_stack.append(margin)
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000228 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):
234 if self.margin_stack:
235 del self.margin_stack[-1]
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000236 fstack = filter(None, self.margin_stack)
237 if fstack:
238 margin = fstack[-1]
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000239 else:
240 margin = None
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000241 self.writer.new_margin(margin, len(fstack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000242
243 def set_spacing(self, spacing):
244 self.spacing = spacing
245 self.writer.new_spacing(spacing)
246
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000247 def push_style(self, *styles):
248 if self.softspace:
Guido van Rossum8e449911996-08-26 16:19:23 +0000249 self.hard_break = self.para_end = self.softspace = 0
250 self.nospace = 1
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000251 self.writer.send_flowing_data(' ')
252 for style in styles:
253 self.style_stack.append(style)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000254 self.writer.new_styles(tuple(self.style_stack))
255
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000256 def pop_style(self, n=1):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000257 del self.style_stack[-n:]
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000258 self.writer.new_styles(tuple(self.style_stack))
259
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000260 def assert_line_data(self, flag=1):
261 self.nospace = self.hard_break = not flag
Guido van Rossum8e449911996-08-26 16:19:23 +0000262 self.para_end = self.parskip = self.have_label = 0
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000263
264
265class NullWriter:
266 """Minimal writer interface to use in testing.
267 """
Guido van Rossum3672aa21996-05-29 00:02:30 +0000268 def __init__(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):
285 pass
286
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000287 def new_alignment(self, align):
288 print "new_alignment(%s)" % `align`
289
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000290 def new_font(self, font):
291 print "new_font(%s)" % `font`
292
293 def new_margin(self, margin, level):
294 print "new_margin(%s, %d)" % (`margin`, level)
295
296 def new_spacing(self, spacing):
297 print "new_spacing(%s)" % `spacing`
298
299 def new_styles(self, styles):
300 print "new_styles(%s)" % `styles`
301
302 def send_paragraph(self, blankline):
303 print "send_paragraph(%s)" % `blankline`
304
305 def send_line_break(self):
306 print "send_line_break()"
307
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000308 def send_hor_rule(self, *args, **kw):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000309 print "send_hor_rule()"
310
311 def send_label_data(self, data):
312 print "send_label_data(%s)" % `data`
313
314 def send_flowing_data(self, data):
315 print "send_flowing_data(%s)" % `data`
316
317 def send_literal_data(self, data):
318 print "send_literal_data(%s)" % `data`
319
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):
324 self.file = file or sys.stdout
325 self.maxcol = maxcol
Guido van Rossum3672aa21996-05-29 00:02:30 +0000326 NullWriter.__init__(self)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000327 self.reset()
328
329 def reset(self):
330 self.col = 0
331 self.atbreak = 0
332
333 def send_paragraph(self, blankline):
334 self.file.write('\n' + '\n'*blankline)
335 self.col = 0
336 self.atbreak = 0
337
338 def send_line_break(self):
339 self.file.write('\n')
340 self.col = 0
341 self.atbreak = 0
342
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000343 def send_hor_rule(self, *args, **kw):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000344 self.file.write('\n')
345 self.file.write('-'*self.maxcol)
346 self.file.write('\n')
347 self.col = 0
348 self.atbreak = 0
349
350 def send_literal_data(self, data):
351 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
359
360 def send_flowing_data(self, data):
361 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
379
380
381def test(file = None):
382 w = DumbWriter()
383 f = AbstractFormatter(w)
384 if file:
385 fp = open(file)
386 elif sys.argv[1:]:
387 fp = open(sys.argv[1])
388 else:
389 fp = sys.stdin
390 while 1:
391 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)
398 f.end_paragraph(0)
399
400
401if __name__ == '__main__':
402 test()