14adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao"""Generic output formatting.
24adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
34adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoFormatter objects transform an abstract flow of formatting events into
44adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaospecific output events on writer objects. Formatters manage several stack
54adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaostructures to allow various properties of a writer object to be changed and
64adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaorestored; writers need not be able to handle relative changes nor any sort
74adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoof ``change back'' operation. Specific writer properties which may be
84adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaocontrolled via formatter objects are horizontal alignment, font, and left
94adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaomargin indentations. A mechanism is provided which supports providing
104adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoarbitrary, non-exclusive style settings to a writer as well. Additional
114adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaointerfaces facilitate formatting events which are not reversible, such as
124adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoparagraph separation.
134adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
144adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoWriter objects encapsulate device interfaces. Abstract devices, such as
154adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaofile formats, are supported as well as physical devices. The provided
164adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoimplementations all work with abstract devices. The interface makes
174adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoavailable mechanisms for setting the properties which formatter objects
184adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaomanage and inserting data into the output.
194adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao"""
204adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
214adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoimport sys
224adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
234adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
244adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoAS_IS = None
254adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
264adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
274adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoclass NullFormatter:
284adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    """A formatter which does nothing.
294adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
304adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    If the writer parameter is omitted, a NullWriter instance is created.
314adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    No methods of the writer are called by NullFormatter instances.
324adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
334adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    Implementations should inherit from this class if implementing a writer
344adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    interface but don't need to inherit any implementation.
354adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
364adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    """
374adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
384adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def __init__(self, writer=None):
394adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if writer is None:
404adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            writer = NullWriter()
414adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.writer = writer
424adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def end_paragraph(self, blankline): pass
434adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def add_line_break(self): pass
444adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def add_hor_rule(self, *args, **kw): pass
454adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def add_label_data(self, format, counter, blankline=None): pass
464adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def add_flowing_data(self, data): pass
474adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def add_literal_data(self, data): pass
484adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def flush_softspace(self): pass
494adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def push_alignment(self, align): pass
504adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def pop_alignment(self): pass
514adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def push_font(self, x): pass
524adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def pop_font(self): pass
534adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def push_margin(self, margin): pass
544adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def pop_margin(self): pass
554adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def set_spacing(self, spacing): pass
564adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def push_style(self, *styles): pass
574adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def pop_style(self, n=1): pass
584adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def assert_line_data(self, flag=1): pass
594adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
604adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
614adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoclass AbstractFormatter:
624adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    """The standard formatter.
634adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
644adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    This implementation has demonstrated wide applicability to many writers,
654adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    and may be used directly in most circumstances.  It has been used to
664adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    implement a full-featured World Wide Web browser.
674adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
684adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    """
694adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
704adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    #  Space handling policy:  blank spaces at the boundary between elements
714adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    #  are handled by the outermost context.  "Literal" data is not checked
724adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    #  to determine context, so spaces in literal data are handled directly
734adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    #  in all circumstances.
744adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
754adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def __init__(self, writer):
764adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.writer = writer            # Output device
774adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.align = None               # Current alignment
784adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.align_stack = []           # Alignment stack
794adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.font_stack = []            # Font state
804adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.margin_stack = []          # Margin state
814adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.spacing = None             # Vertical spacing state
824adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.style_stack = []           # Other state, e.g. color
834adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.nospace = 1                # Should leading space be suppressed
844adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.softspace = 0              # Should a space be inserted
854adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.para_end = 1               # Just ended a paragraph
864adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.parskip = 0                # Skipped space between paragraphs?
874adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.hard_break = 1             # Have a hard break
884adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.have_label = 0
894adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
904adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def end_paragraph(self, blankline):
914adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if not self.hard_break:
924adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.send_line_break()
934adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.have_label = 0
944adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.parskip < blankline and not self.have_label:
954adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.send_paragraph(blankline - self.parskip)
964adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.parskip = blankline
974adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.have_label = 0
984adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.hard_break = self.nospace = self.para_end = 1
994adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.softspace = 0
1004adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1014adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def add_line_break(self):
1024adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if not (self.hard_break or self.para_end):
1034adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.send_line_break()
1044adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.have_label = self.parskip = 0
1054adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.hard_break = self.nospace = 1
1064adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.softspace = 0
1074adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1084adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def add_hor_rule(self, *args, **kw):
1094adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if not self.hard_break:
1104adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.send_line_break()
1114adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.writer.send_hor_rule(*args, **kw)
1124adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.hard_break = self.nospace = 1
1134adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.have_label = self.para_end = self.softspace = self.parskip = 0
1144adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1154adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def add_label_data(self, format, counter, blankline = None):
1164adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.have_label or not self.hard_break:
1174adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.send_line_break()
1184adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if not self.para_end:
1194adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.send_paragraph((blankline and 1) or 0)
1204adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if isinstance(format, str):
1214adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.send_label_data(self.format_counter(format, counter))
1224adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        else:
1234adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.send_label_data(format)
1244adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.nospace = self.have_label = self.hard_break = self.para_end = 1
1254adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.softspace = self.parskip = 0
1264adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1274adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def format_counter(self, format, counter):
1284adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        label = ''
1294adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        for c in format:
1304adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if c == '1':
1314adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                label = label + ('%d' % counter)
1324adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            elif c in 'aA':
1334adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                if counter > 0:
1344adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    label = label + self.format_letter(c, counter)
1354adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            elif c in 'iI':
1364adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                if counter > 0:
1374adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    label = label + self.format_roman(c, counter)
1384adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            else:
1394adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                label = label + c
1404adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        return label
1414adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1424adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def format_letter(self, case, counter):
1434adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        label = ''
1444adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        while counter > 0:
1454adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            counter, x = divmod(counter-1, 26)
1464adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            # This makes a strong assumption that lowercase letters
1474adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            # and uppercase letters form two contiguous blocks, with
1484adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            # letters in order!
1494adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            s = chr(ord(case) + x)
1504adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            label = s + label
1514adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        return label
1524adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1534adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def format_roman(self, case, counter):
1544adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        ones = ['i', 'x', 'c', 'm']
1554adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        fives = ['v', 'l', 'd']
1564adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        label, index = '', 0
1574adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        # This will die of IndexError when counter is too big
1584adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        while counter > 0:
1594adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            counter, x = divmod(counter, 10)
1604adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if x == 9:
1614adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                label = ones[index] + ones[index+1] + label
1624adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            elif x == 4:
1634adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                label = ones[index] + fives[index] + label
1644adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            else:
1654adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                if x >= 5:
1664adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    s = fives[index]
1674adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    x = x-5
1684adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                else:
1694adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    s = ''
1704adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                s = s + ones[index]*x
1714adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                label = s + label
1724adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            index = index + 1
1734adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if case == 'I':
1744adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            return label.upper()
1754adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        return label
1764adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1774adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def add_flowing_data(self, data):
1784adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if not data: return
1794adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        prespace = data[:1].isspace()
1804adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        postspace = data[-1:].isspace()
1814adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        data = " ".join(data.split())
1824adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.nospace and not data:
1834adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            return
1844adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        elif prespace or self.softspace:
1854adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if not data:
1864adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                if not self.nospace:
1874adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    self.softspace = 1
1884adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    self.parskip = 0
1894adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                return
1904adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if not self.nospace:
1914adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                data = ' ' + data
1924adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.hard_break = self.nospace = self.para_end = \
1934adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                          self.parskip = self.have_label = 0
1944adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.softspace = postspace
1954adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.writer.send_flowing_data(data)
1964adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1974adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def add_literal_data(self, data):
1984adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if not data: return
1994adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.softspace:
2004adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.send_flowing_data(" ")
2014adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.hard_break = data[-1:] == '\n'
2024adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.nospace = self.para_end = self.softspace = \
2034adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                       self.parskip = self.have_label = 0
2044adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.writer.send_literal_data(data)
2054adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2064adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def flush_softspace(self):
2074adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.softspace:
2084adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.hard_break = self.para_end = self.parskip = \
2094adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                              self.have_label = self.softspace = 0
2104adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.nospace = 1
2114adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.send_flowing_data(' ')
2124adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2134adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def push_alignment(self, align):
2144adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if align and align != self.align:
2154adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.new_alignment(align)
2164adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.align = align
2174adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.align_stack.append(align)
2184adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        else:
2194adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.align_stack.append(self.align)
2204adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2214adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def pop_alignment(self):
2224adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.align_stack:
2234adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            del self.align_stack[-1]
2244adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.align_stack:
2254adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.align = align = self.align_stack[-1]
2264adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.new_alignment(align)
2274adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        else:
2284adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.align = None
2294adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.new_alignment(None)
2304adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2314adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def push_font(self, font):
2324adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        size, i, b, tt = font
2334adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.softspace:
2344adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.hard_break = self.para_end = self.softspace = 0
2354adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.nospace = 1
2364adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.send_flowing_data(' ')
2374adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.font_stack:
2384adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            csize, ci, cb, ctt = self.font_stack[-1]
2394adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if size is AS_IS: size = csize
2404adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if i is AS_IS: i = ci
2414adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if b is AS_IS: b = cb
2424adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if tt is AS_IS: tt = ctt
2434adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        font = (size, i, b, tt)
2444adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.font_stack.append(font)
2454adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.writer.new_font(font)
2464adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2474adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def pop_font(self):
2484adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.font_stack:
2494adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            del self.font_stack[-1]
2504adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.font_stack:
2514adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            font = self.font_stack[-1]
2524adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        else:
2534adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            font = None
2544adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.writer.new_font(font)
2554adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2564adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def push_margin(self, margin):
2574adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.margin_stack.append(margin)
2584adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        fstack = filter(None, self.margin_stack)
2594adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if not margin and fstack:
2604adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            margin = fstack[-1]
2614adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.writer.new_margin(margin, len(fstack))
2624adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2634adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def pop_margin(self):
2644adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.margin_stack:
2654adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            del self.margin_stack[-1]
2664adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        fstack = filter(None, self.margin_stack)
2674adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if fstack:
2684adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            margin = fstack[-1]
2694adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        else:
2704adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            margin = None
2714adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.writer.new_margin(margin, len(fstack))
2724adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2734adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def set_spacing(self, spacing):
2744adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.spacing = spacing
2754adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.writer.new_spacing(spacing)
2764adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2774adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def push_style(self, *styles):
2784adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if self.softspace:
2794adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.hard_break = self.para_end = self.softspace = 0
2804adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.nospace = 1
2814adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.writer.send_flowing_data(' ')
2824adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        for style in styles:
2834adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.style_stack.append(style)
2844adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.writer.new_styles(tuple(self.style_stack))
2854adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2864adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def pop_style(self, n=1):
2874adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        del self.style_stack[-n:]
2884adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.writer.new_styles(tuple(self.style_stack))
2894adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2904adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def assert_line_data(self, flag=1):
2914adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.nospace = self.hard_break = not flag
2924adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.para_end = self.parskip = self.have_label = 0
2934adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2944adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2954adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoclass NullWriter:
2964adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    """Minimal writer interface to use in testing & inheritance.
2974adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
2984adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    A writer which only provides the interface definition; no actions are
2994adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    taken on any methods.  This should be the base class for all writers
3004adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    which do not need to inherit any implementation methods.
3014adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3024adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    """
3034adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def __init__(self): pass
3044adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def flush(self): pass
3054adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def new_alignment(self, align): pass
3064adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def new_font(self, font): pass
3074adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def new_margin(self, margin, level): pass
3084adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def new_spacing(self, spacing): pass
3094adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def new_styles(self, styles): pass
3104adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_paragraph(self, blankline): pass
3114adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_line_break(self): pass
3124adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_hor_rule(self, *args, **kw): pass
3134adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_label_data(self, data): pass
3144adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_flowing_data(self, data): pass
3154adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_literal_data(self, data): pass
3164adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3174adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3184adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoclass AbstractWriter(NullWriter):
3194adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    """A writer which can be used in debugging formatters, but not much else.
3204adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3214adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    Each method simply announces itself by printing its name and
3224adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    arguments on standard output.
3234adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3244adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    """
3254adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3264adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def new_alignment(self, align):
3274adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        print "new_alignment(%r)" % (align,)
3284adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3294adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def new_font(self, font):
3304adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        print "new_font(%r)" % (font,)
3314adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3324adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def new_margin(self, margin, level):
3334adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        print "new_margin(%r, %d)" % (margin, level)
3344adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3354adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def new_spacing(self, spacing):
3364adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        print "new_spacing(%r)" % (spacing,)
3374adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3384adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def new_styles(self, styles):
3394adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        print "new_styles(%r)" % (styles,)
3404adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3414adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_paragraph(self, blankline):
3424adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        print "send_paragraph(%r)" % (blankline,)
3434adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3444adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_line_break(self):
3454adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        print "send_line_break()"
3464adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3474adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_hor_rule(self, *args, **kw):
3484adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        print "send_hor_rule()"
3494adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3504adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_label_data(self, data):
3514adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        print "send_label_data(%r)" % (data,)
3524adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3534adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_flowing_data(self, data):
3544adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        print "send_flowing_data(%r)" % (data,)
3554adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3564adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_literal_data(self, data):
3574adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        print "send_literal_data(%r)" % (data,)
3584adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3594adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3604adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoclass DumbWriter(NullWriter):
3614adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    """Simple writer class which writes output on the file object passed in
3624adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    as the file parameter or, if file is omitted, on standard output.  The
3634adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    output is simply word-wrapped to the number of columns specified by
3644adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    the maxcol parameter.  This class is suitable for reflowing a sequence
3654adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    of paragraphs.
3664adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3674adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    """
3684adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3694adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def __init__(self, file=None, maxcol=72):
3704adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.file = file or sys.stdout
3714adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.maxcol = maxcol
3724adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        NullWriter.__init__(self)
3734adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.reset()
3744adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3754adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def reset(self):
3764adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.col = 0
3774adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.atbreak = 0
3784adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3794adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_paragraph(self, blankline):
3804adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.file.write('\n'*blankline)
3814adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.col = 0
3824adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.atbreak = 0
3834adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3844adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_line_break(self):
3854adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.file.write('\n')
3864adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.col = 0
3874adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.atbreak = 0
3884adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3894adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_hor_rule(self, *args, **kw):
3904adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.file.write('\n')
3914adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.file.write('-'*self.maxcol)
3924adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.file.write('\n')
3934adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.col = 0
3944adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.atbreak = 0
3954adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
3964adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_literal_data(self, data):
3974adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.file.write(data)
3984adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        i = data.rfind('\n')
3994adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if i >= 0:
4004adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.col = 0
4014adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            data = data[i+1:]
4024adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        data = data.expandtabs()
4034adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.col = self.col + len(data)
4044adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.atbreak = 0
4054adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
4064adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def send_flowing_data(self, data):
4074adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if not data: return
4084adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        atbreak = self.atbreak or data[0].isspace()
4094adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        col = self.col
4104adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        maxcol = self.maxcol
4114adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        write = self.file.write
4124adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        for word in data.split():
4134adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if atbreak:
4144adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                if col + len(word) >= maxcol:
4154adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    write('\n')
4164adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    col = 0
4174adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                else:
4184adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    write(' ')
4194adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    col = col + 1
4204adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            write(word)
4214adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            col = col + len(word)
4224adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            atbreak = 1
4234adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.col = col
4244adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self.atbreak = data[-1].isspace()
4254adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
4264adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
4274adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaodef test(file = None):
4284adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    w = DumbWriter()
4294adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    f = AbstractFormatter(w)
4304adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    if file is not None:
4314adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        fp = open(file)
4324adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    elif sys.argv[1:]:
4334adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        fp = open(sys.argv[1])
4344adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    else:
4354adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        fp = sys.stdin
4364adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    for line in fp:
4374adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if line == '\n':
4384adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            f.end_paragraph(1)
4394adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        else:
4404adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            f.add_flowing_data(line)
4414adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    f.end_paragraph(0)
4424adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
4434adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
4444adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoif __name__ == '__main__':
4454adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    test()
446