home *** CD-ROM | disk | FTP | other *** search
/ linuxmafia.com 2016 / linuxmafia.com.tar / linuxmafia.com / pub / palmos / pippy-0.6beta-src.tar.gz / pippy-0.6beta-src.tar / pippy-0.6beta-src / src / Lib / formatter.py < prev    next >
Text File  |  2000-12-21  |  14KB  |  423 lines

  1. """Generic output formatting.
  2.  
  3. Formatter objects transform an abstract flow of formatting events into
  4. specific output events on writer objects. Formatters manage several stack
  5. structures to allow various properties of a writer object to be changed and
  6. restored; writers need not be able to handle relative changes nor any sort
  7. of ``change back'' operation. Specific writer properties which may be
  8. controlled via formatter objects are horizontal alignment, font, and left
  9. margin indentations. A mechanism is provided which supports providing
  10. arbitrary, non-exclusive style settings to a writer as well. Additional
  11. interfaces facilitate formatting events which are not reversible, such as
  12. paragraph separation. 
  13.  
  14. Writer objects encapsulate device interfaces. Abstract devices, such as
  15. file formats, are supported as well as physical devices. The provided
  16. implementations all work with abstract devices. The interface makes
  17. available mechanisms for setting the properties which formatter objects
  18. manage and inserting data into the output. 
  19. """
  20.  
  21. import string
  22. import sys
  23. from types import StringType
  24.  
  25.  
  26. AS_IS = None
  27.  
  28.  
  29. class NullFormatter:
  30.  
  31.     def __init__(self, writer=None):
  32.         if not writer:
  33.             writer = NullWriter()
  34.         self.writer = writer
  35.     def end_paragraph(self, blankline): pass
  36.     def add_line_break(self): pass
  37.     def add_hor_rule(self, *args, **kw): pass
  38.     def add_label_data(self, format, counter, blankline=None): pass
  39.     def add_flowing_data(self, data): pass
  40.     def add_literal_data(self, data): pass
  41.     def flush_softspace(self): pass
  42.     def push_alignment(self, align): pass
  43.     def pop_alignment(self): pass
  44.     def push_font(self, x): pass
  45.     def pop_font(self): pass
  46.     def push_margin(self, margin): pass
  47.     def pop_margin(self): pass
  48.     def set_spacing(self, spacing): pass
  49.     def push_style(self, *styles): pass
  50.     def pop_style(self, n=1): pass
  51.     def assert_line_data(self, flag=1): pass
  52.  
  53.  
  54. class AbstractFormatter:
  55.  
  56.     #  Space handling policy:  blank spaces at the boundary between elements
  57.     #  are handled by the outermost context.  "Literal" data is not checked
  58.     #  to determine context, so spaces in literal data are handled directly
  59.     #  in all circumstances.
  60.  
  61.     def __init__(self, writer):
  62.         self.writer = writer            # Output device
  63.         self.align = None               # Current alignment
  64.         self.align_stack = []           # Alignment stack
  65.         self.font_stack = []            # Font state
  66.         self.margin_stack = []          # Margin state
  67.         self.spacing = None             # Vertical spacing state
  68.         self.style_stack = []           # Other state, e.g. color
  69.         self.nospace = 1                # Should leading space be suppressed
  70.         self.softspace = 0              # Should a space be inserted
  71.         self.para_end = 1               # Just ended a paragraph
  72.         self.parskip = 0                # Skipped space between paragraphs?
  73.         self.hard_break = 1             # Have a hard break
  74.         self.have_label = 0
  75.  
  76.     def end_paragraph(self, blankline):
  77.         if not self.hard_break:
  78.             self.writer.send_line_break()
  79.             self.have_label = 0
  80.         if self.parskip < blankline and not self.have_label:
  81.             self.writer.send_paragraph(blankline - self.parskip)
  82.             self.parskip = blankline
  83.             self.have_label = 0
  84.         self.hard_break = self.nospace = self.para_end = 1
  85.         self.softspace = 0
  86.  
  87.     def add_line_break(self):
  88.         if not (self.hard_break or self.para_end):
  89.             self.writer.send_line_break()
  90.             self.have_label = self.parskip = 0
  91.         self.hard_break = self.nospace = 1
  92.         self.softspace = 0
  93.  
  94.     def add_hor_rule(self, *args, **kw):
  95.         if not self.hard_break:
  96.             self.writer.send_line_break()
  97.         apply(self.writer.send_hor_rule, args, kw)
  98.         self.hard_break = self.nospace = 1
  99.         self.have_label = self.para_end = self.softspace = self.parskip = 0
  100.  
  101.     def add_label_data(self, format, counter, blankline = None):
  102.         if self.have_label or not self.hard_break:
  103.             self.writer.send_line_break()
  104.         if not self.para_end:
  105.             self.writer.send_paragraph((blankline and 1) or 0)
  106.         if type(format) is StringType:
  107.             self.writer.send_label_data(self.format_counter(format, counter))
  108.         else:
  109.             self.writer.send_label_data(format)
  110.         self.nospace = self.have_label = self.hard_break = self.para_end = 1
  111.         self.softspace = self.parskip = 0
  112.  
  113.     def format_counter(self, format, counter):
  114.         label = ''
  115.         for c in format:
  116.             try:
  117.                 if c == '1':
  118.                     label = label + ('%d' % counter)
  119.                 elif c in 'aA':
  120.                     if counter > 0:
  121.                         label = label + self.format_letter(c, counter)
  122.                 elif c in 'iI':
  123.                     if counter > 0:
  124.                         label = label + self.format_roman(c, counter)
  125.                 else:
  126.                     label = label + c
  127.             except:
  128.                 label = label + c
  129.         return label
  130.  
  131.     def format_letter(self, case, counter):
  132.         label = ''
  133.         while counter > 0:
  134.             counter, x = divmod(counter-1, 26)
  135.             s = chr(ord(case) + x)
  136.             label = s + label
  137.         return label
  138.  
  139.     def format_roman(self, case, counter):
  140.         ones = ['i', 'x', 'c', 'm']
  141.         fives = ['v', 'l', 'd']
  142.         label, index = '', 0
  143.         # This will die of IndexError when counter is too big
  144.         while counter > 0:
  145.             counter, x = divmod(counter, 10)
  146.             if x == 9:
  147.                 label = ones[index] + ones[index+1] + label
  148.             elif x == 4:
  149.                 label = ones[index] + fives[index] + label
  150.             else:
  151.                 if x >= 5:
  152.                     s = fives[index]
  153.                     x = x-5
  154.                 else:
  155.                     s = ''
  156.                 s = s + ones[index]*x
  157.                 label = s + label
  158.             index = index + 1
  159.         if case == 'I':
  160.             return string.upper(label)
  161.         return label
  162.  
  163.     def add_flowing_data(self, data,
  164.                          # These are only here to load them into locals:
  165.                          whitespace = string.whitespace,
  166.                          join = string.join, split = string.split):
  167.         if not data: return
  168.         # The following looks a bit convoluted but is a great improvement over
  169.         # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
  170.         prespace = data[:1] in whitespace
  171.         postspace = data[-1:] in whitespace
  172.         data = join(split(data))
  173.         if self.nospace and not data:
  174.             return
  175.         elif prespace or self.softspace:
  176.             if not data:
  177.                 if not self.nospace:
  178.                     self.softspace = 1
  179.                     self.parskip = 0
  180.                 return
  181.             if not self.nospace:
  182.                 data = ' ' + data
  183.         self.hard_break = self.nospace = self.para_end = \
  184.                           self.parskip = self.have_label = 0
  185.         self.softspace = postspace
  186.         self.writer.send_flowing_data(data)
  187.  
  188.     def add_literal_data(self, data):
  189.         if not data: return
  190.         if self.softspace:
  191.             self.writer.send_flowing_data(" ")
  192.         self.hard_break = data[-1:] == '\n'
  193.         self.nospace = self.para_end = self.softspace = \
  194.                        self.parskip = self.have_label = 0
  195.         self.writer.send_literal_data(data)
  196.  
  197.     def flush_softspace(self):
  198.         if self.softspace:
  199.             self.hard_break = self.para_end = self.parskip = \
  200.                               self.have_label = self.softspace = 0
  201.             self.nospace = 1
  202.             self.writer.send_flowing_data(' ')
  203.  
  204.     def push_alignment(self, align):
  205.         if align and align != self.align:
  206.             self.writer.new_alignment(align)
  207.             self.align = align
  208.             self.align_stack.append(align)
  209.         else:
  210.             self.align_stack.append(self.align)
  211.  
  212.     def pop_alignment(self):
  213.         if self.align_stack:
  214.             del self.align_stack[-1]
  215.         if self.align_stack:
  216.             self.align = align = self.align_stack[-1]
  217.             self.writer.new_alignment(align)
  218.         else:
  219.             self.align = None
  220.             self.writer.new_alignment(None)
  221.  
  222.     def push_font(self, (size, i, b, tt)):
  223.         if self.softspace:
  224.             self.hard_break = self.para_end = self.softspace = 0
  225.             self.nospace = 1
  226.             self.writer.send_flowing_data(' ')
  227.         if self.font_stack:
  228.             csize, ci, cb, ctt = self.font_stack[-1]
  229.             if size is AS_IS: size = csize
  230.             if i is AS_IS: i = ci
  231.             if b is AS_IS: b = cb
  232.             if tt is AS_IS: tt = ctt
  233.         font = (size, i, b, tt)
  234.         self.font_stack.append(font)
  235.         self.writer.new_font(font)
  236.  
  237.     def pop_font(self):
  238.         if self.font_stack:
  239.             del self.font_stack[-1]
  240.         if self.font_stack:
  241.             font = self.font_stack[-1]
  242.         else:
  243.             font = None
  244.         self.writer.new_font(font)
  245.  
  246.     def push_margin(self, margin):
  247.         self.margin_stack.append(margin)
  248.         fstack = filter(None, self.margin_stack)
  249.         if not margin and fstack:
  250.             margin = fstack[-1]
  251.         self.writer.new_margin(margin, len(fstack))
  252.  
  253.     def pop_margin(self):
  254.         if self.margin_stack:
  255.             del self.margin_stack[-1]
  256.         fstack = filter(None, self.margin_stack)
  257.         if fstack:
  258.             margin = fstack[-1]
  259.         else:
  260.             margin = None
  261.         self.writer.new_margin(margin, len(fstack))
  262.  
  263.     def set_spacing(self, spacing):
  264.         self.spacing = spacing
  265.         self.writer.new_spacing(spacing)
  266.  
  267.     def push_style(self, *styles):
  268.         if self.softspace:
  269.             self.hard_break = self.para_end = self.softspace = 0
  270.             self.nospace = 1
  271.             self.writer.send_flowing_data(' ')
  272.         for style in styles:
  273.             self.style_stack.append(style)
  274.         self.writer.new_styles(tuple(self.style_stack))
  275.  
  276.     def pop_style(self, n=1):
  277.         del self.style_stack[-n:]
  278.         self.writer.new_styles(tuple(self.style_stack))
  279.  
  280.     def assert_line_data(self, flag=1):
  281.         self.nospace = self.hard_break = not flag
  282.         self.para_end = self.parskip = self.have_label = 0
  283.  
  284.  
  285. class NullWriter:
  286.     """Minimal writer interface to use in testing & inheritance."""
  287.     def __init__(self): pass
  288.     def flush(self): pass
  289.     def new_alignment(self, align): pass
  290.     def new_font(self, font): pass
  291.     def new_margin(self, margin, level): pass
  292.     def new_spacing(self, spacing): pass
  293.     def new_styles(self, styles): pass
  294.     def send_paragraph(self, blankline): pass
  295.     def send_line_break(self): pass
  296.     def send_hor_rule(self, *args, **kw): pass
  297.     def send_label_data(self, data): pass
  298.     def send_flowing_data(self, data): pass
  299.     def send_literal_data(self, data): pass
  300.  
  301.  
  302. class AbstractWriter(NullWriter):
  303.  
  304.     def __init__(self):
  305.         pass
  306.  
  307.     def new_alignment(self, align):
  308.         print "new_alignment(%s)" % `align`
  309.  
  310.     def new_font(self, font):
  311.         print "new_font(%s)" % `font`
  312.  
  313.     def new_margin(self, margin, level):
  314.         print "new_margin(%s, %d)" % (`margin`, level)
  315.  
  316.     def new_spacing(self, spacing):
  317.         print "new_spacing(%s)" % `spacing`
  318.  
  319.     def new_styles(self, styles):
  320.         print "new_styles(%s)" % `styles`
  321.  
  322.     def send_paragraph(self, blankline):
  323.         print "send_paragraph(%s)" % `blankline`
  324.  
  325.     def send_line_break(self):
  326.         print "send_line_break()"
  327.  
  328.     def send_hor_rule(self, *args, **kw):
  329.         print "send_hor_rule()"
  330.  
  331.     def send_label_data(self, data):
  332.         print "send_label_data(%s)" % `data`
  333.  
  334.     def send_flowing_data(self, data):
  335.         print "send_flowing_data(%s)" % `data`
  336.  
  337.     def send_literal_data(self, data):
  338.         print "send_literal_data(%s)" % `data`
  339.  
  340.  
  341. class DumbWriter(NullWriter):
  342.  
  343.     def __init__(self, file=None, maxcol=72):
  344.         self.file = file or sys.stdout
  345.         self.maxcol = maxcol
  346.         NullWriter.__init__(self)
  347.         self.reset()
  348.  
  349.     def reset(self):
  350.         self.col = 0
  351.         self.atbreak = 0
  352.  
  353.     def send_paragraph(self, blankline):
  354.         self.file.write('\n'*blankline)
  355.         self.col = 0
  356.         self.atbreak = 0
  357.  
  358.     def send_line_break(self):
  359.         self.file.write('\n')
  360.         self.col = 0
  361.         self.atbreak = 0
  362.  
  363.     def send_hor_rule(self, *args, **kw):
  364.         self.file.write('\n')
  365.         self.file.write('-'*self.maxcol)
  366.         self.file.write('\n')
  367.         self.col = 0
  368.         self.atbreak = 0
  369.  
  370.     def send_literal_data(self, data):
  371.         self.file.write(data)
  372.         i = string.rfind(data, '\n')
  373.         if i >= 0:
  374.             self.col = 0
  375.             data = data[i+1:]
  376.         data = string.expandtabs(data)
  377.         self.col = self.col + len(data)
  378.         self.atbreak = 0
  379.  
  380.     def send_flowing_data(self, data):
  381.         if not data: return
  382.         atbreak = self.atbreak or data[0] in string.whitespace
  383.         col = self.col
  384.         maxcol = self.maxcol
  385.         write = self.file.write
  386.         for word in string.split(data):
  387.             if atbreak:
  388.                 if col + len(word) >= maxcol:
  389.                     write('\n')
  390.                     col = 0
  391.                 else:
  392.                     write(' ')
  393.                     col = col + 1
  394.             write(word)
  395.             col = col + len(word)
  396.             atbreak = 1
  397.         self.col = col
  398.         self.atbreak = data[-1] in string.whitespace
  399.  
  400.  
  401. def test(file = None):
  402.     w = DumbWriter()
  403.     f = AbstractFormatter(w)
  404.     if file:
  405.         fp = open(file)
  406.     elif sys.argv[1:]:
  407.         fp = open(sys.argv[1])
  408.     else:
  409.         fp = sys.stdin
  410.     while 1:
  411.         line = fp.readline()
  412.         if not line:
  413.             break
  414.         if line == '\n':
  415.             f.end_paragraph(1)
  416.         else:
  417.             f.add_flowing_data(line)
  418.     f.end_paragraph(0)
  419.  
  420.  
  421. if __name__ == '__main__':
  422.     test()
  423.