blob: d1830e4c3dc1b0f05579b351c881979866fa2e4a [file] [log] [blame]
Guido van Rossuma0eab1d1995-08-07 20:07:36 +00001import regex
2import regsub
3import string
4import sys
5
6
7AS_IS = None
8
Guido van Rossuma0eab1d1995-08-07 20:07:36 +00009
Guido van Rossum909507d1995-10-06 15:31:30 +000010class NullFormatter:
11
12 def __init__(self): pass
13 def end_paragraph(self, blankline): pass
14 def add_line_break(self): pass
15 def add_hor_rule(self): pass
16 def add_label_data(self, format, counter): pass
17 def add_flowing_data(self, data): pass
18 def add_literal_data(self, data): pass
19 def flush_softspace(self): pass
20 def push_font(self, x): pass
21 def pop_font(self): pass
22 def push_margin(self, margin): pass
23 def pop_margin(self): pass
24 def set_spacing(self, spacing): pass
25 def push_style(self, style): pass
26 def pop_style(self): pass
27
28
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000029class AbstractFormatter:
30
31 def __init__(self, writer):
32 self.writer = writer # Output device
33 self.font_stack = [] # Font state
34 self.margin_stack = [] # Margin state
35 self.spacing = None # Vertical spacing state
36 self.style_stack = [] # Other state, e.g. color
Guido van Rossum909507d1995-10-06 15:31:30 +000037 self.nospace = 1 # Should leading space be suppressed
38 self.softspace = 0 # Should a space be inserted
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000039
40 def end_paragraph(self, blankline):
41 if not self.nospace:
42 self.writer.send_paragraph(blankline)
43 self.nospace = 1
44 self.softspace = 0
45
46 def add_line_break(self):
47 self.writer.send_line_break()
48 self.nospace = 1
49 self.softspace = 0
50
51 def add_hor_rule(self):
52 self.writer.send_hor_rule()
53 self.nospace = 1
54 self.softspace = 0
55
56 def add_label_data(self, format, counter):
57 data = self.format_counter(format, counter)
58 self.writer.send_label_data(data)
59
60 def format_counter(self, format, counter):
61 if counter <= 0:
62 return format
63 label = ''
64 for c in format:
65 try:
66 if c == '1':
67 c = '%d' % counter
68 elif c in 'aA':
69 c = self.format_letter(c, counter)
70 elif c in 'iI':
71 c = self.format_roman(c, counter)
72 except:
73 pass
74 label = label + c
75 return label
76
77 def format_letter(self, case, counter):
78 label = ''
79 while counter > 0:
80 counter, x = divmod(counter-1, 26)
81 s = chr(ord(case) + x)
82 label = s + label
83 return label
84
85 def format_roman(self, case, counter):
86 ones = ['i', 'x', 'c', 'm']
87 fives = ['v', 'l', 'd']
88 label = ''
89 index = 0
90 # This will die of IndexError when counter is too big
91 while counter > 0:
92 counter, x = divmod(counter, 10)
93 if x == 9:
94 s = ones[index] + ones[index+1]
95 elif x == 4:
96 s = ones[index] + fives[index]
97 else:
98 if x >= 5:
99 s = fives[index]
100 x = x-5
101 else:
102 s = ''
103 s = s + ones[index]*x
104 label = s + label
105 index = index + 1
106 if case == 'I': label = string.upper(label)
107 return label
108
109 def add_flowing_data(self, data):
110 if not data: return
Guido van Rossum93dc8011996-02-12 23:59:54 +0000111 # The following looks a bit convoluted but is a great improvement over
112 # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
Guido van Rossum7bb7ecd1996-02-13 00:01:28 +0000113 prespace = data[0] in string.whitespace
114 postspace = data[-1] in string.whitespace
115 data = string.join(string.split(data))
116 if self.nospace and prespace:
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000117 if not data: return
Guido van Rossum7bb7ecd1996-02-13 00:01:28 +0000118 prespace = 0
119 elif self.softspace:
120 prespace = 1
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000121 self.nospace = self.softspace = 0
Guido van Rossum7bb7ecd1996-02-13 00:01:28 +0000122 if postspace:
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000123 self.softspace = 1
Guido van Rossum7bb7ecd1996-02-13 00:01:28 +0000124 if prespace: data = ' ' + data
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000125 self.writer.send_flowing_data(data)
126
127 def add_literal_data(self, data):
128 if self.softspace and data[:1] != '\n':
129 data = ' ' + data
130 self.nospace = self.softspace = 0
131 self.writer.send_literal_data(data)
132
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000133 def flush_softspace(self):
134 if self.softspace:
135 self.nospace = self.softspace = 0
136 self.writer.send_flowing_data(' ')
137
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000138 def push_font(self, (size, i, b, tt)):
139 if self.font_stack:
140 csize, ci, cb, ctt = self.font_stack[-1]
141 if size is AS_IS: size = csize
142 if i is AS_IS: i = ci
143 if b is AS_IS: b = cb
144 if tt is AS_IS: tt = ctt
145 font = (size, i, b, tt)
146 self.font_stack.append(font)
147 self.writer.new_font(font)
148
149 def pop_font(self):
150 if self.font_stack:
151 del self.font_stack[-1]
152 if self.font_stack:
153 font = self.font_stack[-1]
154 else:
155 font = None
156 self.writer.new_font(font)
157
158 def push_margin(self, margin):
159 self.margin_stack.append(margin)
160 self.writer.new_margin(margin, len(self.margin_stack))
161
162 def pop_margin(self):
163 if self.margin_stack:
164 del self.margin_stack[-1]
165 if self.margin_stack:
166 margin = self.margin_stack[-1]
167 else:
168 margin = None
169 self.writer.new_margin(margin, len(self.margin_stack))
170
171 def set_spacing(self, spacing):
172 self.spacing = spacing
173 self.writer.new_spacing(spacing)
174
175 def push_style(self, style):
176 self.style_stack.append(style)
177 self.writer.new_styles(tuple(self.style_stack))
178
179 def pop_style(self):
180 if self.style_stack:
181 del self.style_stack[-1]
182 self.writer.new_styles(tuple(self.style_stack))
183
184
185class AbstractWriter:
186
187 def __init__(self):
188 pass
189
190 def new_font(self, font):
191 print "new_font(%s)" % `font`
192
193 def new_margin(self, margin, level):
194 print "new_margin(%s, %d)" % (`margin`, level)
195
196 def new_spacing(self, spacing):
197 print "new_spacing(%s)" % `spacing`
198
199 def new_styles(self, styles):
200 print "new_styles(%s)" % `styles`
201
202 def send_paragraph(self, blankline):
203 print "send_paragraph(%s)" % `blankline`
204
205 def send_line_break(self):
206 print "send_line_break()"
207
208 def send_hor_rule(self):
209 print "send_hor_rule()"
210
211 def send_label_data(self, data):
212 print "send_label_data(%s)" % `data`
213
214 def send_flowing_data(self, data):
215 print "send_flowing_data(%s)" % `data`
216
217 def send_literal_data(self, data):
218 print "send_literal_data(%s)" % `data`
219
220
221class DumbWriter(AbstractWriter):
222
223 def __init__(self, file=None, maxcol=72):
224 self.file = file or sys.stdout
225 self.maxcol = maxcol
226 AbstractWriter.__init__(self)
227 self.reset()
228
229 def reset(self):
230 self.col = 0
231 self.atbreak = 0
232
233 def send_paragraph(self, blankline):
234 self.file.write('\n' + '\n'*blankline)
235 self.col = 0
236 self.atbreak = 0
237
238 def send_line_break(self):
239 self.file.write('\n')
240 self.col = 0
241 self.atbreak = 0
242
243 def send_hor_rule(self):
244 self.file.write('\n')
245 self.file.write('-'*self.maxcol)
246 self.file.write('\n')
247 self.col = 0
248 self.atbreak = 0
249
250 def send_literal_data(self, data):
251 self.file.write(data)
252 i = string.rfind(data, '\n')
253 if i >= 0:
254 self.col = 0
255 data = data[i+1:]
256 data = string.expandtabs(data)
257 self.col = self.col + len(data)
258 self.atbreak = 0
259
260 def send_flowing_data(self, data):
261 if not data: return
262 atbreak = self.atbreak or data[0] in string.whitespace
263 col = self.col
264 maxcol = self.maxcol
265 write = self.file.write
266 for word in string.split(data):
267 if atbreak:
268 if col + len(word) >= maxcol:
269 write('\n')
270 col = 0
271 else:
272 write(' ')
273 col = col + 1
274 write(word)
275 col = col + len(word)
276 atbreak = 1
277 self.col = col
278 self.atbreak = data[-1] in string.whitespace
279
280
281def test(file = None):
282 w = DumbWriter()
283 f = AbstractFormatter(w)
284 if file:
285 fp = open(file)
286 elif sys.argv[1:]:
287 fp = open(sys.argv[1])
288 else:
289 fp = sys.stdin
290 while 1:
291 line = fp.readline()
292 if not line:
293 break
294 if line == '\n':
295 f.end_paragraph(1)
296 else:
297 f.add_flowing_data(line)
298 f.end_paragraph(0)
299
300
301if __name__ == '__main__':
302 test()