| import regex | 
 | import regsub | 
 | import string | 
 | import sys | 
 | from types import StringType | 
 |  | 
 |  | 
 | 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, abswidth=None, percentwidth=1.0, | 
 | 		     height=None, align=None): 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_alignment(self, align): pass | 
 |     def pop_alignment(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, *styles): pass | 
 |     def pop_style(self, n=1): pass | 
 |     def assert_line_data(self, flag=1): pass | 
 |  | 
 |  | 
 | class AbstractFormatter: | 
 |  | 
 |     def __init__(self, writer): | 
 | 	self.writer = writer		# Output device | 
 | 	self.align = None		# Current alignment | 
 | 	self.align_stack = []		# Alignment stack | 
 | 	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 | 
 | 	self.para_end = 1		# Just ended a paragraph | 
 | 	self.parskip = 0		# Skipped space between paragraphs? | 
 | 	self.hard_break = 1		# Have a hard break | 
 | 	self.have_label = 0 | 
 |  | 
 |     def end_paragraph(self, blankline): | 
 | 	if not self.hard_break: | 
 | 	    self.writer.send_line_break() | 
 | 	    self.have_label = 0 | 
 | 	if self.parskip < blankline and not self.have_label: | 
 | 	    self.writer.send_paragraph(blankline - self.parskip) | 
 | 	    self.parskip = blankline | 
 | 	    self.have_label = 0 | 
 | 	self.hard_break = self.nospace = self.para_end = 1 | 
 | 	self.softspace = 0 | 
 |  | 
 |     def add_line_break(self): | 
 | 	if not (self.hard_break or self.para_end): | 
 | 	    self.writer.send_line_break() | 
 | 	    self.have_label = self.parskip = 0 | 
 | 	self.hard_break = self.nospace = 1 | 
 | 	self.softspace = 0 | 
 |  | 
 |     def add_hor_rule(self, *args, **kw): | 
 | 	if not self.hard_break: | 
 | 	    self.writer.send_line_break() | 
 | 	apply(self.writer.send_hor_rule, args, kw) | 
 | 	self.hard_break = self.nospace = 1 | 
 | 	self.have_label = self.para_end = self.softspace = self.parskip = 0 | 
 |  | 
 |     def add_label_data(self, format, counter, blankline = None): | 
 | 	if self.have_label or not self.hard_break: | 
 | 	    self.writer.send_line_break() | 
 | 	if not self.para_end: | 
 | 	    self.writer.send_paragraph((blankline and 1) or 0) | 
 | 	if type(format) is StringType: | 
 | 	    self.writer.send_label_data(self.format_counter(format, counter)) | 
 | 	else: | 
 | 	    self.writer.send_label_data(format) | 
 | 	self.nospace = self.have_label = self.hard_break = self.para_end = 1 | 
 | 	self.softspace = self.parskip = 0 | 
 |  | 
 |     def format_counter(self, format, counter): | 
 |         label = '' | 
 |         for c in format: | 
 |             try: | 
 |                 if c == '1': | 
 | 		    label = label + ('%d' % counter) | 
 |                 elif c in 'aA': | 
 | 		    if counter > 0: | 
 | 			label = label + self.format_letter(c, counter) | 
 |                 elif c in 'iI': | 
 | 		    if counter > 0: | 
 | 			label = label + self.format_roman(c, counter) | 
 | 		else: | 
 | 		    label = label + c | 
 |             except: | 
 |                 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: | 
 |                 label = ones[index] + ones[index+1] + label | 
 |             elif x == 4: | 
 |                 label = ones[index] + fives[index] + label | 
 |             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': | 
 | 	    return string.upper(label) | 
 |         return label | 
 |  | 
 |     def add_flowing_data(self, data, | 
 | 			 # These are only here to load them into locals: | 
 | 			 whitespace = string.whitespace, | 
 | 			 join = string.join, split = string.split): | 
 | 	if not data: return | 
 | 	# The following looks a bit convoluted but is a great improvement over | 
 | 	# data = regsub.gsub('[' + string.whitespace + ']+', ' ', data) | 
 | 	prespace = data[:1] in whitespace | 
 | 	postspace = data[-1:] in whitespace | 
 | 	data = join(split(data)) | 
 | 	if self.nospace and not data: | 
 | 	    return | 
 | 	elif prespace or self.softspace: | 
 | 	    if not data: | 
 | 		if not self.nospace: | 
 | 		    self.softspace = 1 | 
 | 		    self.parskip = 0 | 
 | 		return | 
 | 	    if not self.nospace: | 
 | 		data = ' ' + data | 
 | 	self.hard_break = self.nospace = self.para_end = \ | 
 | 			  self.parskip = self.have_label = 0 | 
 | 	self.softspace = postspace | 
 | 	self.writer.send_flowing_data(data) | 
 |  | 
 |     def add_literal_data(self, data): | 
 | 	if not data: return | 
 | 	#  Caller is expected to cause flush_softspace() if needed. | 
 | 	self.hard_break = data[-1:] == '\n' | 
 | 	self.nospace = self.para_end = self.softspace = \ | 
 | 		       self.parskip = self.have_label = 0 | 
 | 	self.writer.send_literal_data(data) | 
 |  | 
 |     def flush_softspace(self): | 
 | 	if self.softspace: | 
 | 	    self.hard_break = self.nospace = self.para_end = self.parskip = \ | 
 | 			      self.have_label = self.softspace = 0 | 
 | 	    self.writer.send_flowing_data(' ') | 
 |  | 
 |     def push_alignment(self, align): | 
 | 	if align and align != self.align: | 
 | 	    self.writer.new_alignment(align) | 
 | 	    self.align = align | 
 | 	    self.align_stack.append(align) | 
 | 	else: | 
 | 	    self.align_stack.append(self.align) | 
 |  | 
 |     def pop_alignment(self): | 
 | 	if self.align_stack: | 
 | 	    del self.align_stack[-1] | 
 | 	if self.align_stack: | 
 | 	    self.align = align = self.align_stack[-1] | 
 | 	    self.writer.new_alignment(align) | 
 | 	else: | 
 | 	    self.align = None | 
 | 	    self.writer.new_alignment(None) | 
 |  | 
 |     def push_font(self, (size, i, b, tt)): | 
 | 	if self.softspace: | 
 | 	    self.hard_break = self.nospace = self.para_end = self.softspace = 0 | 
 | 	    self.writer.send_flowing_data(' ') | 
 | 	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.softspace: | 
 | 	    self.hard_break = self.nospace = self.para_end = self.softspace = 0 | 
 | 	    self.writer.send_flowing_data(' ') | 
 | 	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) | 
 | 	fstack = filter(None, self.margin_stack) | 
 | 	if not margin and fstack: | 
 | 	    margin = fstack[-1] | 
 | 	self.writer.new_margin(margin, len(fstack)) | 
 |  | 
 |     def pop_margin(self): | 
 | 	if self.margin_stack: | 
 | 	    del self.margin_stack[-1] | 
 | 	fstack = filter(None, self.margin_stack) | 
 | 	if fstack: | 
 | 	    margin = fstack[-1] | 
 | 	else: | 
 | 	    margin = None | 
 | 	self.writer.new_margin(margin, len(fstack)) | 
 |  | 
 |     def set_spacing(self, spacing): | 
 | 	self.spacing = spacing | 
 | 	self.writer.new_spacing(spacing) | 
 |  | 
 |     def push_style(self, *styles): | 
 | 	if self.softspace: | 
 | 	    self.hard_break = self.nospace = self.para_end = self.softspace = 0 | 
 | 	    self.writer.send_flowing_data(' ') | 
 | 	for style in styles: | 
 | 	    self.style_stack.append(style) | 
 | 	self.writer.new_styles(tuple(self.style_stack)) | 
 |  | 
 |     def pop_style(self, n=1): | 
 | 	if self.softspace: | 
 | 	    self.hard_break = self.nospace = self.para_end = self.softspace = 0 | 
 | 	    self.writer.send_flowing_data(' ') | 
 | 	del self.style_stack[-n:] | 
 | 	self.writer.new_styles(tuple(self.style_stack)) | 
 |  | 
 |     def assert_line_data(self, flag=1): | 
 | 	self.nospace = self.hard_break = not flag | 
 | 	self.para_end = self.have_label = 0 | 
 |  | 
 |  | 
 | class NullWriter: | 
 |     """Minimal writer interface to use in testing. | 
 |     """ | 
 |     def __init__(self): pass | 
 |     def new_alignment(self, align): pass | 
 |     def new_font(self, font): pass | 
 |     def new_margin(self, margin, level): pass | 
 |     def new_spacing(self, spacing): pass | 
 |     def new_styles(self, styles): pass | 
 |     def send_paragraph(self, blankline): pass | 
 |     def send_line_break(self): pass | 
 |     def send_hor_rule(self, *args, **kw): pass | 
 |     def send_label_data(self, data): pass | 
 |     def send_flowing_data(self, data): pass | 
 |     def send_literal_data(self, data): pass | 
 |  | 
 |  | 
 | class AbstractWriter(NullWriter): | 
 |  | 
 |     def __init__(self): | 
 | 	pass | 
 |  | 
 |     def new_alignment(self, align): | 
 | 	print "new_alignment(%s)" % `align` | 
 |  | 
 |     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, *args, **kw): | 
 | 	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(NullWriter): | 
 |  | 
 |     def __init__(self, file=None, maxcol=72): | 
 | 	self.file = file or sys.stdout | 
 | 	self.maxcol = maxcol | 
 | 	NullWriter.__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, *args, **kw): | 
 | 	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() |