PyXR

c:\python24\lib \ formatter.py



0001 """Generic output formatting.
0002 
0003 Formatter objects transform an abstract flow of formatting events into
0004 specific output events on writer objects. Formatters manage several stack
0005 structures to allow various properties of a writer object to be changed and
0006 restored; writers need not be able to handle relative changes nor any sort
0007 of ``change back'' operation. Specific writer properties which may be
0008 controlled via formatter objects are horizontal alignment, font, and left
0009 margin indentations. A mechanism is provided which supports providing
0010 arbitrary, non-exclusive style settings to a writer as well. Additional
0011 interfaces facilitate formatting events which are not reversible, such as
0012 paragraph separation.
0013 
0014 Writer objects encapsulate device interfaces. Abstract devices, such as
0015 file formats, are supported as well as physical devices. The provided
0016 implementations all work with abstract devices. The interface makes
0017 available mechanisms for setting the properties which formatter objects
0018 manage and inserting data into the output.
0019 """
0020 
0021 import sys
0022 
0023 
0024 AS_IS = None
0025 
0026 
0027 class NullFormatter:
0028     """A formatter which does nothing.
0029 
0030     If the writer parameter is omitted, a NullWriter instance is created.
0031     No methods of the writer are called by NullFormatter instances.
0032 
0033     Implementations should inherit from this class if implementing a writer
0034     interface but don't need to inherit any implementation.
0035 
0036     """
0037 
0038     def __init__(self, writer=None):
0039         if writer is None:
0040             writer = NullWriter()
0041         self.writer = writer
0042     def end_paragraph(self, blankline): pass
0043     def add_line_break(self): pass
0044     def add_hor_rule(self, *args, **kw): pass
0045     def add_label_data(self, format, counter, blankline=None): pass
0046     def add_flowing_data(self, data): pass
0047     def add_literal_data(self, data): pass
0048     def flush_softspace(self): pass
0049     def push_alignment(self, align): pass
0050     def pop_alignment(self): pass
0051     def push_font(self, x): pass
0052     def pop_font(self): pass
0053     def push_margin(self, margin): pass
0054     def pop_margin(self): pass
0055     def set_spacing(self, spacing): pass
0056     def push_style(self, *styles): pass
0057     def pop_style(self, n=1): pass
0058     def assert_line_data(self, flag=1): pass
0059 
0060 
0061 class AbstractFormatter:
0062     """The standard formatter.
0063 
0064     This implementation has demonstrated wide applicability to many writers,
0065     and may be used directly in most circumstances.  It has been used to
0066     implement a full-featured World Wide Web browser.
0067 
0068     """
0069 
0070     #  Space handling policy:  blank spaces at the boundary between elements
0071     #  are handled by the outermost context.  "Literal" data is not checked
0072     #  to determine context, so spaces in literal data are handled directly
0073     #  in all circumstances.
0074 
0075     def __init__(self, writer):
0076         self.writer = writer            # Output device
0077         self.align = None               # Current alignment
0078         self.align_stack = []           # Alignment stack
0079         self.font_stack = []            # Font state
0080         self.margin_stack = []          # Margin state
0081         self.spacing = None             # Vertical spacing state
0082         self.style_stack = []           # Other state, e.g. color
0083         self.nospace = 1                # Should leading space be suppressed
0084         self.softspace = 0              # Should a space be inserted
0085         self.para_end = 1               # Just ended a paragraph
0086         self.parskip = 0                # Skipped space between paragraphs?
0087         self.hard_break = 1             # Have a hard break
0088         self.have_label = 0
0089 
0090     def end_paragraph(self, blankline):
0091         if not self.hard_break:
0092             self.writer.send_line_break()
0093             self.have_label = 0
0094         if self.parskip < blankline and not self.have_label:
0095             self.writer.send_paragraph(blankline - self.parskip)
0096             self.parskip = blankline
0097             self.have_label = 0
0098         self.hard_break = self.nospace = self.para_end = 1
0099         self.softspace = 0
0100 
0101     def add_line_break(self):
0102         if not (self.hard_break or self.para_end):
0103             self.writer.send_line_break()
0104             self.have_label = self.parskip = 0
0105         self.hard_break = self.nospace = 1
0106         self.softspace = 0
0107 
0108     def add_hor_rule(self, *args, **kw):
0109         if not self.hard_break:
0110             self.writer.send_line_break()
0111         self.writer.send_hor_rule(*args, **kw)
0112         self.hard_break = self.nospace = 1
0113         self.have_label = self.para_end = self.softspace = self.parskip = 0
0114 
0115     def add_label_data(self, format, counter, blankline = None):
0116         if self.have_label or not self.hard_break:
0117             self.writer.send_line_break()
0118         if not self.para_end:
0119             self.writer.send_paragraph((blankline and 1) or 0)
0120         if isinstance(format, str):
0121             self.writer.send_label_data(self.format_counter(format, counter))
0122         else:
0123             self.writer.send_label_data(format)
0124         self.nospace = self.have_label = self.hard_break = self.para_end = 1
0125         self.softspace = self.parskip = 0
0126 
0127     def format_counter(self, format, counter):
0128         label = ''
0129         for c in format:
0130             if c == '1':
0131                 label = label + ('%d' % counter)
0132             elif c in 'aA':
0133                 if counter > 0:
0134                     label = label + self.format_letter(c, counter)
0135             elif c in 'iI':
0136                 if counter > 0:
0137                     label = label + self.format_roman(c, counter)
0138             else:
0139                 label = label + c
0140         return label
0141 
0142     def format_letter(self, case, counter):
0143         label = ''
0144         while counter > 0:
0145             counter, x = divmod(counter-1, 26)
0146             # This makes a strong assumption that lowercase letters
0147             # and uppercase letters form two contiguous blocks, with
0148             # letters in order!
0149             s = chr(ord(case) + x)
0150             label = s + label
0151         return label
0152 
0153     def format_roman(self, case, counter):
0154         ones = ['i', 'x', 'c', 'm']
0155         fives = ['v', 'l', 'd']
0156         label, index = '', 0
0157         # This will die of IndexError when counter is too big
0158         while counter > 0:
0159             counter, x = divmod(counter, 10)
0160             if x == 9:
0161                 label = ones[index] + ones[index+1] + label
0162             elif x == 4:
0163                 label = ones[index] + fives[index] + label
0164             else:
0165                 if x >= 5:
0166                     s = fives[index]
0167                     x = x-5
0168                 else:
0169                     s = ''
0170                 s = s + ones[index]*x
0171                 label = s + label
0172             index = index + 1
0173         if case == 'I':
0174             return label.upper()
0175         return label
0176 
0177     def add_flowing_data(self, data):
0178         if not data: return
0179         # The following looks a bit convoluted but is a great improvement over
0180         # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
0181         prespace = data[:1].isspace()
0182         postspace = data[-1:].isspace()
0183         data = " ".join(data.split())
0184         if self.nospace and not data:
0185             return
0186         elif prespace or self.softspace:
0187             if not data:
0188                 if not self.nospace:
0189                     self.softspace = 1
0190                     self.parskip = 0
0191                 return
0192             if not self.nospace:
0193                 data = ' ' + data
0194         self.hard_break = self.nospace = self.para_end = \
0195                           self.parskip = self.have_label = 0
0196         self.softspace = postspace
0197         self.writer.send_flowing_data(data)
0198 
0199     def add_literal_data(self, data):
0200         if not data: return
0201         if self.softspace:
0202             self.writer.send_flowing_data(" ")
0203         self.hard_break = data[-1:] == '\n'
0204         self.nospace = self.para_end = self.softspace = \
0205                        self.parskip = self.have_label = 0
0206         self.writer.send_literal_data(data)
0207 
0208     def flush_softspace(self):
0209         if self.softspace:
0210             self.hard_break = self.para_end = self.parskip = \
0211                               self.have_label = self.softspace = 0
0212             self.nospace = 1
0213             self.writer.send_flowing_data(' ')
0214 
0215     def push_alignment(self, align):
0216         if align and align != self.align:
0217             self.writer.new_alignment(align)
0218             self.align = align
0219             self.align_stack.append(align)
0220         else:
0221             self.align_stack.append(self.align)
0222 
0223     def pop_alignment(self):
0224         if self.align_stack:
0225             del self.align_stack[-1]
0226         if self.align_stack:
0227             self.align = align = self.align_stack[-1]
0228             self.writer.new_alignment(align)
0229         else:
0230             self.align = None
0231             self.writer.new_alignment(None)
0232 
0233     def push_font(self, (size, i, b, tt)):
0234         if self.softspace:
0235             self.hard_break = self.para_end = self.softspace = 0
0236             self.nospace = 1
0237             self.writer.send_flowing_data(' ')
0238         if self.font_stack:
0239             csize, ci, cb, ctt = self.font_stack[-1]
0240             if size is AS_IS: size = csize
0241             if i is AS_IS: i = ci
0242             if b is AS_IS: b = cb
0243             if tt is AS_IS: tt = ctt
0244         font = (size, i, b, tt)
0245         self.font_stack.append(font)
0246         self.writer.new_font(font)
0247 
0248     def pop_font(self):
0249         if self.font_stack:
0250             del self.font_stack[-1]
0251         if self.font_stack:
0252             font = self.font_stack[-1]
0253         else:
0254             font = None
0255         self.writer.new_font(font)
0256 
0257     def push_margin(self, margin):
0258         self.margin_stack.append(margin)
0259         fstack = filter(None, self.margin_stack)
0260         if not margin and fstack:
0261             margin = fstack[-1]
0262         self.writer.new_margin(margin, len(fstack))
0263 
0264     def pop_margin(self):
0265         if self.margin_stack:
0266             del self.margin_stack[-1]
0267         fstack = filter(None, self.margin_stack)
0268         if fstack:
0269             margin = fstack[-1]
0270         else:
0271             margin = None
0272         self.writer.new_margin(margin, len(fstack))
0273 
0274     def set_spacing(self, spacing):
0275         self.spacing = spacing
0276         self.writer.new_spacing(spacing)
0277 
0278     def push_style(self, *styles):
0279         if self.softspace:
0280             self.hard_break = self.para_end = self.softspace = 0
0281             self.nospace = 1
0282             self.writer.send_flowing_data(' ')
0283         for style in styles:
0284             self.style_stack.append(style)
0285         self.writer.new_styles(tuple(self.style_stack))
0286 
0287     def pop_style(self, n=1):
0288         del self.style_stack[-n:]
0289         self.writer.new_styles(tuple(self.style_stack))
0290 
0291     def assert_line_data(self, flag=1):
0292         self.nospace = self.hard_break = not flag
0293         self.para_end = self.parskip = self.have_label = 0
0294 
0295 
0296 class NullWriter:
0297     """Minimal writer interface to use in testing & inheritance.
0298 
0299     A writer which only provides the interface definition; no actions are
0300     taken on any methods.  This should be the base class for all writers
0301     which do not need to inherit any implementation methods.
0302 
0303     """
0304     def __init__(self): pass
0305     def flush(self): pass
0306     def new_alignment(self, align): pass
0307     def new_font(self, font): pass
0308     def new_margin(self, margin, level): pass
0309     def new_spacing(self, spacing): pass
0310     def new_styles(self, styles): pass
0311     def send_paragraph(self, blankline): pass
0312     def send_line_break(self): pass
0313     def send_hor_rule(self, *args, **kw): pass
0314     def send_label_data(self, data): pass
0315     def send_flowing_data(self, data): pass
0316     def send_literal_data(self, data): pass
0317 
0318 
0319 class AbstractWriter(NullWriter):
0320     """A writer which can be used in debugging formatters, but not much else.
0321 
0322     Each method simply announces itself by printing its name and
0323     arguments on standard output.
0324 
0325     """
0326 
0327     def new_alignment(self, align):
0328         print "new_alignment(%r)" % (align,)
0329 
0330     def new_font(self, font):
0331         print "new_font(%r)" % (font,)
0332 
0333     def new_margin(self, margin, level):
0334         print "new_margin(%r, %d)" % (margin, level)
0335 
0336     def new_spacing(self, spacing):
0337         print "new_spacing(%r)" % (spacing,)
0338 
0339     def new_styles(self, styles):
0340         print "new_styles(%r)" % (styles,)
0341 
0342     def send_paragraph(self, blankline):
0343         print "send_paragraph(%r)" % (blankline,)
0344 
0345     def send_line_break(self):
0346         print "send_line_break()"
0347 
0348     def send_hor_rule(self, *args, **kw):
0349         print "send_hor_rule()"
0350 
0351     def send_label_data(self, data):
0352         print "send_label_data(%r)" % (data,)
0353 
0354     def send_flowing_data(self, data):
0355         print "send_flowing_data(%r)" % (data,)
0356 
0357     def send_literal_data(self, data):
0358         print "send_literal_data(%r)" % (data,)
0359 
0360 
0361 class DumbWriter(NullWriter):
0362     """Simple writer class which writes output on the file object passed in
0363     as the file parameter or, if file is omitted, on standard output.  The
0364     output is simply word-wrapped to the number of columns specified by
0365     the maxcol parameter.  This class is suitable for reflowing a sequence
0366     of paragraphs.
0367 
0368     """
0369 
0370     def __init__(self, file=None, maxcol=72):
0371         self.file = file or sys.stdout
0372         self.maxcol = maxcol
0373         NullWriter.__init__(self)
0374         self.reset()
0375 
0376     def reset(self):
0377         self.col = 0
0378         self.atbreak = 0
0379 
0380     def send_paragraph(self, blankline):
0381         self.file.write('\n'*blankline)
0382         self.col = 0
0383         self.atbreak = 0
0384 
0385     def send_line_break(self):
0386         self.file.write('\n')
0387         self.col = 0
0388         self.atbreak = 0
0389 
0390     def send_hor_rule(self, *args, **kw):
0391         self.file.write('\n')
0392         self.file.write('-'*self.maxcol)
0393         self.file.write('\n')
0394         self.col = 0
0395         self.atbreak = 0
0396 
0397     def send_literal_data(self, data):
0398         self.file.write(data)
0399         i = data.rfind('\n')
0400         if i >= 0:
0401             self.col = 0
0402             data = data[i+1:]
0403         data = data.expandtabs()
0404         self.col = self.col + len(data)
0405         self.atbreak = 0
0406 
0407     def send_flowing_data(self, data):
0408         if not data: return
0409         atbreak = self.atbreak or data[0].isspace()
0410         col = self.col
0411         maxcol = self.maxcol
0412         write = self.file.write
0413         for word in data.split():
0414             if atbreak:
0415                 if col + len(word) >= maxcol:
0416                     write('\n')
0417                     col = 0
0418                 else:
0419                     write(' ')
0420                     col = col + 1
0421             write(word)
0422             col = col + len(word)
0423             atbreak = 1
0424         self.col = col
0425         self.atbreak = data[-1].isspace()
0426 
0427 
0428 def test(file = None):
0429     w = DumbWriter()
0430     f = AbstractFormatter(w)
0431     if file is not None:
0432         fp = open(file)
0433     elif sys.argv[1:]:
0434         fp = open(sys.argv[1])
0435     else:
0436         fp = sys.stdin
0437     while 1:
0438         line = fp.readline()
0439         if not line:
0440             break
0441         if line == '\n':
0442             f.end_paragraph(1)
0443         else:
0444             f.add_flowing_data(line)
0445     f.end_paragraph(0)
0446 
0447 
0448 if __name__ == '__main__':
0449     test()
0450 

Generated by PyXR 0.9.4
SourceForge.net Logo