blob: 363222e2a608c303480b76057a2161fee0c43637 [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)
113 if data[0] in string.whitespace:
114 head = ' '
115 else:
116 head = ''
117 if data[-1] in string.whitespace:
118 tail = ' '
119 else:
120 tail = ''
121 data = head + string.join(string.split(data))
122 if data != ' ': data = data + tail
123 #
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000124 if self.nospace and data[0] == ' ':
125 data = data[1:]
126 if not data: return
127 elif self.softspace and data[0] != ' ':
128 data = ' ' + data
129 self.nospace = self.softspace = 0
130 if data[-1] == ' ':
131 data = data[:-1]
132 self.softspace = 1
133 self.writer.send_flowing_data(data)
134
135 def add_literal_data(self, data):
136 if self.softspace and data[:1] != '\n':
137 data = ' ' + data
138 self.nospace = self.softspace = 0
139 self.writer.send_literal_data(data)
140
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000141 def flush_softspace(self):
142 if self.softspace:
143 self.nospace = self.softspace = 0
144 self.writer.send_flowing_data(' ')
145
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000146 def push_font(self, (size, i, b, tt)):
147 if self.font_stack:
148 csize, ci, cb, ctt = self.font_stack[-1]
149 if size is AS_IS: size = csize
150 if i is AS_IS: i = ci
151 if b is AS_IS: b = cb
152 if tt is AS_IS: tt = ctt
153 font = (size, i, b, tt)
154 self.font_stack.append(font)
155 self.writer.new_font(font)
156
157 def pop_font(self):
158 if self.font_stack:
159 del self.font_stack[-1]
160 if self.font_stack:
161 font = self.font_stack[-1]
162 else:
163 font = None
164 self.writer.new_font(font)
165
166 def push_margin(self, margin):
167 self.margin_stack.append(margin)
168 self.writer.new_margin(margin, len(self.margin_stack))
169
170 def pop_margin(self):
171 if self.margin_stack:
172 del self.margin_stack[-1]
173 if self.margin_stack:
174 margin = self.margin_stack[-1]
175 else:
176 margin = None
177 self.writer.new_margin(margin, len(self.margin_stack))
178
179 def set_spacing(self, spacing):
180 self.spacing = spacing
181 self.writer.new_spacing(spacing)
182
183 def push_style(self, style):
184 self.style_stack.append(style)
185 self.writer.new_styles(tuple(self.style_stack))
186
187 def pop_style(self):
188 if self.style_stack:
189 del self.style_stack[-1]
190 self.writer.new_styles(tuple(self.style_stack))
191
192
193class AbstractWriter:
194
195 def __init__(self):
196 pass
197
198 def new_font(self, font):
199 print "new_font(%s)" % `font`
200
201 def new_margin(self, margin, level):
202 print "new_margin(%s, %d)" % (`margin`, level)
203
204 def new_spacing(self, spacing):
205 print "new_spacing(%s)" % `spacing`
206
207 def new_styles(self, styles):
208 print "new_styles(%s)" % `styles`
209
210 def send_paragraph(self, blankline):
211 print "send_paragraph(%s)" % `blankline`
212
213 def send_line_break(self):
214 print "send_line_break()"
215
216 def send_hor_rule(self):
217 print "send_hor_rule()"
218
219 def send_label_data(self, data):
220 print "send_label_data(%s)" % `data`
221
222 def send_flowing_data(self, data):
223 print "send_flowing_data(%s)" % `data`
224
225 def send_literal_data(self, data):
226 print "send_literal_data(%s)" % `data`
227
228
229class DumbWriter(AbstractWriter):
230
231 def __init__(self, file=None, maxcol=72):
232 self.file = file or sys.stdout
233 self.maxcol = maxcol
234 AbstractWriter.__init__(self)
235 self.reset()
236
237 def reset(self):
238 self.col = 0
239 self.atbreak = 0
240
241 def send_paragraph(self, blankline):
242 self.file.write('\n' + '\n'*blankline)
243 self.col = 0
244 self.atbreak = 0
245
246 def send_line_break(self):
247 self.file.write('\n')
248 self.col = 0
249 self.atbreak = 0
250
251 def send_hor_rule(self):
252 self.file.write('\n')
253 self.file.write('-'*self.maxcol)
254 self.file.write('\n')
255 self.col = 0
256 self.atbreak = 0
257
258 def send_literal_data(self, data):
259 self.file.write(data)
260 i = string.rfind(data, '\n')
261 if i >= 0:
262 self.col = 0
263 data = data[i+1:]
264 data = string.expandtabs(data)
265 self.col = self.col + len(data)
266 self.atbreak = 0
267
268 def send_flowing_data(self, data):
269 if not data: return
270 atbreak = self.atbreak or data[0] in string.whitespace
271 col = self.col
272 maxcol = self.maxcol
273 write = self.file.write
274 for word in string.split(data):
275 if atbreak:
276 if col + len(word) >= maxcol:
277 write('\n')
278 col = 0
279 else:
280 write(' ')
281 col = col + 1
282 write(word)
283 col = col + len(word)
284 atbreak = 1
285 self.col = col
286 self.atbreak = data[-1] in string.whitespace
287
288
289def test(file = None):
290 w = DumbWriter()
291 f = AbstractFormatter(w)
292 if file:
293 fp = open(file)
294 elif sys.argv[1:]:
295 fp = open(sys.argv[1])
296 else:
297 fp = sys.stdin
298 while 1:
299 line = fp.readline()
300 if not line:
301 break
302 if line == '\n':
303 f.end_paragraph(1)
304 else:
305 f.add_flowing_data(line)
306 f.end_paragraph(0)
307
308
309if __name__ == '__main__':
310 test()