blob: 25dbe73d78cb418625f47652be015ad8cbade9c2 [file] [log] [blame]
Guido van Rossum5c971671996-07-22 15:23:25 +00001import regex
2import regsub
3import string
4import sys
5from types import StringType
6
7
8AS_IS = None
9
10
11class NullFormatter:
12
Guido van Rossumb8cc6ae1996-10-08 14:13:43 +000013 def __init__(self, writer=None):
14 if not writer:
15 writer = NullWriter()
16 self.writer = writer
Guido van Rossum5c971671996-07-22 15:23:25 +000017 def end_paragraph(self, blankline): pass
18 def add_line_break(self): pass
Guido van Rossumb6685dc1996-10-21 18:10:19 +000019 def add_hor_rule(self, *args, **kw): pass
20 def add_label_data(self, format, counter, blankline=None): pass
Guido van Rossum5c971671996-07-22 15:23:25 +000021 def add_flowing_data(self, data): pass
22 def add_literal_data(self, data): pass
23 def flush_softspace(self): pass
24 def push_alignment(self, align): pass
25 def pop_alignment(self): pass
26 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
31 def push_style(self, *styles): pass
32 def pop_style(self, n=1): pass
33 def assert_line_data(self, flag=1): pass
34
35
36class AbstractFormatter:
37
Guido van Rossuma8763e51996-08-26 18:33:32 +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 Rossum5c971671996-07-22 15:23:25 +000043 def __init__(self, writer):
44 self.writer = writer # Output device
45 self.align = None # Current alignment
46 self.align_stack = [] # Alignment stack
47 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
51 self.nospace = 1 # Should leading space be suppressed
52 self.softspace = 0 # Should a space be inserted
53 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
57
58 def end_paragraph(self, blankline):
59 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
67 self.softspace = 0
68
69 def add_line_break(self):
70 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
74 self.softspace = 0
75
76 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
82
83 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
94
95 def format_counter(self, format, counter):
96 label = ''
97 for c in format:
98 try:
99 if c == '1':
100 label = label + ('%d' % counter)
101 elif c in 'aA':
102 if counter > 0:
103 label = label + self.format_letter(c, counter)
104 elif c in 'iI':
105 if counter > 0:
106 label = label + self.format_roman(c, counter)
107 else:
108 label = label + c
109 except:
110 label = label + c
111 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']
124 label, index = '', 0
125 # This will die of IndexError when counter is too big
126 while counter > 0:
127 counter, x = divmod(counter, 10)
128 if x == 9:
129 label = ones[index] + ones[index+1] + label
130 elif x == 4:
131 label = ones[index] + fives[index] + label
132 else:
133 if x >= 5:
134 s = fives[index]
135 x = x-5
136 else:
137 s = ''
138 s = s + ones[index]*x
139 label = s + label
140 index = index + 1
141 if case == 'I':
142 return string.upper(label)
143 return label
144
145 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):
149 if not data: return
150 # The following looks a bit convoluted but is a great improvement over
151 # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
152 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
168 self.writer.send_flowing_data(data)
169
170 def add_literal_data(self, data):
171 if not data: return
Guido van Rossuma8763e51996-08-26 18:33:32 +0000172 if self.softspace:
173 self.writer.send_flowing_data(" ")
Guido van Rossum5c971671996-07-22 15:23:25 +0000174 self.hard_break = data[-1:] == '\n'
175 self.nospace = self.para_end = self.softspace = \
176 self.parskip = self.have_label = 0
177 self.writer.send_literal_data(data)
178
179 def flush_softspace(self):
180 if self.softspace:
Guido van Rossuma8763e51996-08-26 18:33:32 +0000181 self.hard_break = self.para_end = self.parskip = \
Guido van Rossum5c971671996-07-22 15:23:25 +0000182 self.have_label = self.softspace = 0
Guido van Rossuma8763e51996-08-26 18:33:32 +0000183 self.nospace = 1
Guido van Rossum5c971671996-07-22 15:23:25 +0000184 self.writer.send_flowing_data(' ')
185
186 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
204 def push_font(self, (size, i, b, tt)):
205 if self.softspace:
Guido van Rossuma8763e51996-08-26 18:33:32 +0000206 self.hard_break = self.para_end = self.softspace = 0
207 self.nospace = 1
Guido van Rossum5c971671996-07-22 15:23:25 +0000208 self.writer.send_flowing_data(' ')
209 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):
Guido van Rossum5c971671996-07-22 15:23:25 +0000220 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)
230 fstack = filter(None, self.margin_stack)
231 if not margin and fstack:
232 margin = fstack[-1]
233 self.writer.new_margin(margin, len(fstack))
234
235 def pop_margin(self):
236 if self.margin_stack:
237 del self.margin_stack[-1]
238 fstack = filter(None, self.margin_stack)
239 if fstack:
240 margin = fstack[-1]
241 else:
242 margin = None
243 self.writer.new_margin(margin, len(fstack))
244
245 def set_spacing(self, spacing):
246 self.spacing = spacing
247 self.writer.new_spacing(spacing)
248
249 def push_style(self, *styles):
250 if self.softspace:
Guido van Rossuma8763e51996-08-26 18:33:32 +0000251 self.hard_break = self.para_end = self.softspace = 0
252 self.nospace = 1
Guido van Rossum5c971671996-07-22 15:23:25 +0000253 self.writer.send_flowing_data(' ')
254 for style in styles:
255 self.style_stack.append(style)
256 self.writer.new_styles(tuple(self.style_stack))
257
258 def pop_style(self, n=1):
Guido van Rossum5c971671996-07-22 15:23:25 +0000259 del self.style_stack[-n:]
260 self.writer.new_styles(tuple(self.style_stack))
261
262 def assert_line_data(self, flag=1):
263 self.nospace = self.hard_break = not flag
Guido van Rossuma8763e51996-08-26 18:33:32 +0000264 self.para_end = self.parskip = self.have_label = 0
Guido van Rossum5c971671996-07-22 15:23:25 +0000265
266
267class NullWriter:
Guido van Rossum228b8e81997-04-02 06:13:34 +0000268 """Minimal writer interface to use in testing & inheritance."""
Guido van Rossum5c971671996-07-22 15:23:25 +0000269 def __init__(self): pass
Guido van Rossum228b8e81997-04-02 06:13:34 +0000270 def flush(self): pass
Guido van Rossum5c971671996-07-22 15:23:25 +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
283
284class AbstractWriter(NullWriter):
285
286 def __init__(self):
287 pass
288
289 def new_alignment(self, align):
290 print "new_alignment(%s)" % `align`
291
292 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
310 def send_hor_rule(self, *args, **kw):
311 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
323class DumbWriter(NullWriter):
324
325 def __init__(self, file=None, maxcol=72):
326 self.file = file or sys.stdout
327 self.maxcol = maxcol
328 NullWriter.__init__(self)
329 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
345 def send_hor_rule(self, *args, **kw):
346 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()