home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pytho152.zip / emx / lib / python1.5 / formatter.py < prev    next >
Text File  |  2000-08-10  |  13KB  |  403 lines

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