303 lines
7.2 KiB
Python
303 lines
7.2 KiB
Python
import regex
|
|
import regsub
|
|
import string
|
|
import sys
|
|
|
|
|
|
AS_IS = None
|
|
|
|
|
|
class NullFormatter:
|
|
|
|
def __init__(self): pass
|
|
def end_paragraph(self, blankline): pass
|
|
def add_line_break(self): pass
|
|
def add_hor_rule(self): pass
|
|
def add_label_data(self, format, counter): pass
|
|
def add_flowing_data(self, data): pass
|
|
def add_literal_data(self, data): pass
|
|
def flush_softspace(self): pass
|
|
def push_font(self, x): pass
|
|
def pop_font(self): pass
|
|
def push_margin(self, margin): pass
|
|
def pop_margin(self): pass
|
|
def set_spacing(self, spacing): pass
|
|
def push_style(self, style): pass
|
|
def pop_style(self): pass
|
|
|
|
|
|
class AbstractFormatter:
|
|
|
|
def __init__(self, writer):
|
|
self.writer = writer # Output device
|
|
self.font_stack = [] # Font state
|
|
self.margin_stack = [] # Margin state
|
|
self.spacing = None # Vertical spacing state
|
|
self.style_stack = [] # Other state, e.g. color
|
|
self.nospace = 1 # Should leading space be suppressed
|
|
self.softspace = 0 # Should a space be inserted
|
|
|
|
def end_paragraph(self, blankline):
|
|
if not self.nospace:
|
|
self.writer.send_paragraph(blankline)
|
|
self.nospace = 1
|
|
self.softspace = 0
|
|
|
|
def add_line_break(self):
|
|
self.writer.send_line_break()
|
|
self.nospace = 1
|
|
self.softspace = 0
|
|
|
|
def add_hor_rule(self):
|
|
self.writer.send_hor_rule()
|
|
self.nospace = 1
|
|
self.softspace = 0
|
|
|
|
def add_label_data(self, format, counter):
|
|
data = self.format_counter(format, counter)
|
|
self.writer.send_label_data(data)
|
|
|
|
def format_counter(self, format, counter):
|
|
if counter <= 0:
|
|
return format
|
|
label = ''
|
|
for c in format:
|
|
try:
|
|
if c == '1':
|
|
c = '%d' % counter
|
|
elif c in 'aA':
|
|
c = self.format_letter(c, counter)
|
|
elif c in 'iI':
|
|
c = self.format_roman(c, counter)
|
|
except:
|
|
pass
|
|
label = label + c
|
|
return label
|
|
|
|
def format_letter(self, case, counter):
|
|
label = ''
|
|
while counter > 0:
|
|
counter, x = divmod(counter-1, 26)
|
|
s = chr(ord(case) + x)
|
|
label = s + label
|
|
return label
|
|
|
|
def format_roman(self, case, counter):
|
|
ones = ['i', 'x', 'c', 'm']
|
|
fives = ['v', 'l', 'd']
|
|
label = ''
|
|
index = 0
|
|
# This will die of IndexError when counter is too big
|
|
while counter > 0:
|
|
counter, x = divmod(counter, 10)
|
|
if x == 9:
|
|
s = ones[index] + ones[index+1]
|
|
elif x == 4:
|
|
s = ones[index] + fives[index]
|
|
else:
|
|
if x >= 5:
|
|
s = fives[index]
|
|
x = x-5
|
|
else:
|
|
s = ''
|
|
s = s + ones[index]*x
|
|
label = s + label
|
|
index = index + 1
|
|
if case == 'I': label = string.upper(label)
|
|
return label
|
|
|
|
def add_flowing_data(self, data):
|
|
if not data: return
|
|
# The following looks a bit convoluted but is a great improvement over
|
|
# data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
|
|
prespace = data[0] in string.whitespace
|
|
postspace = data[-1] in string.whitespace
|
|
data = string.join(string.split(data))
|
|
if self.nospace and prespace:
|
|
if not data: return
|
|
prespace = 0
|
|
elif self.softspace:
|
|
prespace = 1
|
|
self.nospace = self.softspace = 0
|
|
if postspace:
|
|
self.softspace = 1
|
|
if prespace: data = ' ' + data
|
|
self.writer.send_flowing_data(data)
|
|
|
|
def add_literal_data(self, data):
|
|
if self.softspace and data[:1] != '\n':
|
|
data = ' ' + data
|
|
self.nospace = self.softspace = 0
|
|
self.writer.send_literal_data(data)
|
|
|
|
def flush_softspace(self):
|
|
if self.softspace:
|
|
self.nospace = self.softspace = 0
|
|
self.writer.send_flowing_data(' ')
|
|
|
|
def push_font(self, (size, i, b, tt)):
|
|
if self.font_stack:
|
|
csize, ci, cb, ctt = self.font_stack[-1]
|
|
if size is AS_IS: size = csize
|
|
if i is AS_IS: i = ci
|
|
if b is AS_IS: b = cb
|
|
if tt is AS_IS: tt = ctt
|
|
font = (size, i, b, tt)
|
|
self.font_stack.append(font)
|
|
self.writer.new_font(font)
|
|
|
|
def pop_font(self):
|
|
if self.font_stack:
|
|
del self.font_stack[-1]
|
|
if self.font_stack:
|
|
font = self.font_stack[-1]
|
|
else:
|
|
font = None
|
|
self.writer.new_font(font)
|
|
|
|
def push_margin(self, margin):
|
|
self.margin_stack.append(margin)
|
|
self.writer.new_margin(margin, len(self.margin_stack))
|
|
|
|
def pop_margin(self):
|
|
if self.margin_stack:
|
|
del self.margin_stack[-1]
|
|
if self.margin_stack:
|
|
margin = self.margin_stack[-1]
|
|
else:
|
|
margin = None
|
|
self.writer.new_margin(margin, len(self.margin_stack))
|
|
|
|
def set_spacing(self, spacing):
|
|
self.spacing = spacing
|
|
self.writer.new_spacing(spacing)
|
|
|
|
def push_style(self, style):
|
|
self.style_stack.append(style)
|
|
self.writer.new_styles(tuple(self.style_stack))
|
|
|
|
def pop_style(self):
|
|
if self.style_stack:
|
|
del self.style_stack[-1]
|
|
self.writer.new_styles(tuple(self.style_stack))
|
|
|
|
|
|
class AbstractWriter:
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
def new_font(self, font):
|
|
print "new_font(%s)" % `font`
|
|
|
|
def new_margin(self, margin, level):
|
|
print "new_margin(%s, %d)" % (`margin`, level)
|
|
|
|
def new_spacing(self, spacing):
|
|
print "new_spacing(%s)" % `spacing`
|
|
|
|
def new_styles(self, styles):
|
|
print "new_styles(%s)" % `styles`
|
|
|
|
def send_paragraph(self, blankline):
|
|
print "send_paragraph(%s)" % `blankline`
|
|
|
|
def send_line_break(self):
|
|
print "send_line_break()"
|
|
|
|
def send_hor_rule(self):
|
|
print "send_hor_rule()"
|
|
|
|
def send_label_data(self, data):
|
|
print "send_label_data(%s)" % `data`
|
|
|
|
def send_flowing_data(self, data):
|
|
print "send_flowing_data(%s)" % `data`
|
|
|
|
def send_literal_data(self, data):
|
|
print "send_literal_data(%s)" % `data`
|
|
|
|
|
|
class DumbWriter(AbstractWriter):
|
|
|
|
def __init__(self, file=None, maxcol=72):
|
|
self.file = file or sys.stdout
|
|
self.maxcol = maxcol
|
|
AbstractWriter.__init__(self)
|
|
self.reset()
|
|
|
|
def reset(self):
|
|
self.col = 0
|
|
self.atbreak = 0
|
|
|
|
def send_paragraph(self, blankline):
|
|
self.file.write('\n' + '\n'*blankline)
|
|
self.col = 0
|
|
self.atbreak = 0
|
|
|
|
def send_line_break(self):
|
|
self.file.write('\n')
|
|
self.col = 0
|
|
self.atbreak = 0
|
|
|
|
def send_hor_rule(self):
|
|
self.file.write('\n')
|
|
self.file.write('-'*self.maxcol)
|
|
self.file.write('\n')
|
|
self.col = 0
|
|
self.atbreak = 0
|
|
|
|
def send_literal_data(self, data):
|
|
self.file.write(data)
|
|
i = string.rfind(data, '\n')
|
|
if i >= 0:
|
|
self.col = 0
|
|
data = data[i+1:]
|
|
data = string.expandtabs(data)
|
|
self.col = self.col + len(data)
|
|
self.atbreak = 0
|
|
|
|
def send_flowing_data(self, data):
|
|
if not data: return
|
|
atbreak = self.atbreak or data[0] in string.whitespace
|
|
col = self.col
|
|
maxcol = self.maxcol
|
|
write = self.file.write
|
|
for word in string.split(data):
|
|
if atbreak:
|
|
if col + len(word) >= maxcol:
|
|
write('\n')
|
|
col = 0
|
|
else:
|
|
write(' ')
|
|
col = col + 1
|
|
write(word)
|
|
col = col + len(word)
|
|
atbreak = 1
|
|
self.col = col
|
|
self.atbreak = data[-1] in string.whitespace
|
|
|
|
|
|
def test(file = None):
|
|
w = DumbWriter()
|
|
f = AbstractFormatter(w)
|
|
if file:
|
|
fp = open(file)
|
|
elif sys.argv[1:]:
|
|
fp = open(sys.argv[1])
|
|
else:
|
|
fp = sys.stdin
|
|
while 1:
|
|
line = fp.readline()
|
|
if not line:
|
|
break
|
|
if line == '\n':
|
|
f.end_paragraph(1)
|
|
else:
|
|
f.add_flowing_data(line)
|
|
f.end_paragraph(0)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
test()
|