From 06d2fd5b90c29cbfe9b938676cc85c514cbbcca1 Mon Sep 17 00:00:00 2001 From: Shlomi Fish Date: Wed, 11 Aug 2021 19:19:01 +0300 Subject: [PATCH] issue217 : formatting.py deprecated fix. This file is derived from /usr/lib/python3.9/formatter.py . All changes are placed under CC0. See: * https://github.com/shlomif/PySolFC/issues/217 * https://bugzilla.redhat.com/show_bug.cgi?id=1990043 --- pysollib/formatter.py | 474 +++++++++++++++++++++++++++++++++++ pysollib/htmllib2.py | 10 +- pysollib/kivy/tkhtml.py | 8 +- pysollib/pysolgtk/tkhtml.py | 8 +- pysollib/ui/tktile/tkhtml.py | 8 +- 5 files changed, 492 insertions(+), 16 deletions(-) create mode 100644 pysollib/formatter.py diff --git a/pysollib/formatter.py b/pysollib/formatter.py new file mode 100644 index 00000000..f572a7bb --- /dev/null +++ b/pysollib/formatter.py @@ -0,0 +1,474 @@ +""" + +This file is derived from /usr/lib/python3.9/formatter.py . All changes +are placed under CC0. + +See: + +* https://github.com/shlomif/PySolFC/issues/217 + +* https://bugzilla.redhat.com/show_bug.cgi?id=1990043 + +---- + +Generic output formatting. + +Formatter objects transform an abstract flow of formatting events into +specific output events on writer objects. Formatters manage several stack +structures to allow various properties of a writer object to be changed and +restored; writers need not be able to handle relative changes nor any sort +of ``change back'' operation. Specific writer properties which may be +controlled via formatter objects are horizontal alignment, font, and left +margin indentations. A mechanism is provided which supports providing +arbitrary, non-exclusive style settings to a writer as well. Additional +interfaces facilitate formatting events which are not reversible, such as +paragraph separation. + +Writer objects encapsulate device interfaces. Abstract devices, such as +file formats, are supported as well as physical devices. The provided +implementations all work with abstract devices. The interface makes +available mechanisms for setting the properties which formatter objects +manage and inserting data into the output. +""" + +import sys +if False: + import warnings + warnings.warn('the formatter module is deprecated', DeprecationWarning, + stacklevel=2) + + +AS_IS = None + + +class NullFormatter: + """A formatter which does nothing. + + If the writer parameter is omitted, a NullWriter instance is created. + No methods of the writer are called by NullFormatter instances. + + Implementations should inherit from this class if implementing a writer + interface but don't need to inherit any implementation. + + """ + + def __init__(self, writer=None): + if writer is None: + writer = NullWriter() + self.writer = writer + + def end_paragraph(self, blankline): pass + def add_line_break(self): pass + def add_hor_rule(self, *args, **kw): pass + def add_label_data(self, format, counter, blankline=None): 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: + """The standard formatter. + + This implementation has demonstrated wide applicability to many writers, + and may be used directly in most circumstances. It has been used to + implement a full-featured World Wide Web browser. + + """ + + # Space handling policy: blank spaces at the boundary between elements + # are handled by the outermost context. "Literal" data is not checked + # to determine context, so spaces in literal data are handled directly + # in all circumstances. + + 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() + 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 isinstance(format, str): + 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: + 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 + return label + + def format_letter(self, case, counter): + label = '' + while counter > 0: + counter, x = divmod(counter-1, 26) + # This makes a strong assumption that lowercase letters + # and uppercase letters form two contiguous blocks, with + # letters in order! + 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 label.upper() + return label + + def add_flowing_data(self, data): + if not data: + return + prespace = data[:1].isspace() + postspace = data[-1:].isspace() + data = " ".join(data.split()) + 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 + if self.softspace: + self.writer.send_flowing_data(" ") + 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.para_end = self.parskip = \ + self.have_label = self.softspace = 0 + self.nospace = 1 + 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, font): + size, i, b, tt = font + if self.softspace: + self.hard_break = self.para_end = self.softspace = 0 + self.nospace = 1 + 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.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 = [m for m in self.margin_stack if m] + 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 = [m for m in self.margin_stack if m] + 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.para_end = self.softspace = 0 + self.nospace = 1 + 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): + 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.parskip = self.have_label = 0 + + +class NullWriter: + """Minimal writer interface to use in testing & inheritance. + + A writer which only provides the interface definition; no actions are + taken on any methods. This should be the base class for all writers + which do not need to inherit any implementation methods. + + """ + def __init__(self): pass + def flush(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): + """A writer which can be used in debugging formatters, but not much else. + + Each method simply announces itself by printing its name and + arguments on standard output. + + """ + + def new_alignment(self, align): + print("new_alignment(%r)" % (align,)) + + def new_font(self, font): + print("new_font(%r)" % (font,)) + + def new_margin(self, margin, level): + print("new_margin(%r, %d)" % (margin, level)) + + def new_spacing(self, spacing): + print("new_spacing(%r)" % (spacing,)) + + def new_styles(self, styles): + print("new_styles(%r)" % (styles,)) + + def send_paragraph(self, blankline): + print("send_paragraph(%r)" % (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(%r)" % (data,)) + + def send_flowing_data(self, data): + print("send_flowing_data(%r)" % (data,)) + + def send_literal_data(self, data): + print("send_literal_data(%r)" % (data,)) + + +class DumbWriter(NullWriter): + """Simple writer class which writes output on the file object passed in + as the file parameter or, if file is omitted, on standard output. The + output is simply word-wrapped to the number of columns specified by + the maxcol parameter. This class is suitable for reflowing a sequence + of paragraphs. + + """ + + 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'*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 = data.rfind('\n') + if i >= 0: + self.col = 0 + data = data[i+1:] + data = data.expandtabs() + 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].isspace() + col = self.col + maxcol = self.maxcol + write = self.file.write + for word in data.split(): + 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].isspace() + + +def test(file=None): + w = DumbWriter() + f = AbstractFormatter(w) + if file is not None: + fp = open(file) + elif sys.argv[1:]: + fp = open(sys.argv[1]) + else: + fp = sys.stdin + try: + for line in fp: + if line == '\n': + f.end_paragraph(1) + else: + f.add_flowing_data(line) + finally: + if fp is not sys.stdin: + fp.close() + f.end_paragraph(0) + + +if __name__ == '__main__': + test() diff --git a/pysollib/htmllib2.py b/pysollib/htmllib2.py index 7ff6b2a9..dab753e7 100644 --- a/pysollib/htmllib2.py +++ b/pysollib/htmllib2.py @@ -4,7 +4,7 @@ http://www.w3.org/hypertext/WWW/MarkUp/html-spec/html-spec_toc.html """ -from formatter import AS_IS +from pysollib.formatter import AS_IS from six.moves import html_parser @@ -483,7 +483,7 @@ def unknown_endtag(self, tag): def test(args=None): import sys - import formatter + import pysollib.formatter if not args: args = sys.argv[1:] @@ -508,9 +508,11 @@ def test(args=None): sys.exit(1) if silent: - f = formatter.NullFormatter() + f = pysollib.formatter.NullFormatter() else: - f = formatter.AbstractFormatter(formatter.DumbWriter()) + f = pysollib.formatter.AbstractFormatter( + pysollib.formatter.DumbWriter() + ) p = HTMLParser(f) p.feed(data) diff --git a/pysollib/kivy/tkhtml.py b/pysollib/kivy/tkhtml.py index 152b281c..54c0db0e 100644 --- a/pysollib/kivy/tkhtml.py +++ b/pysollib/kivy/tkhtml.py @@ -21,7 +21,6 @@ # # ---------------------------------------------------------------------------# -import formatter import os import sys @@ -29,6 +28,7 @@ from kivy.uix.button import Button from kivy.uix.label import Label +import pysollib.formatter import pysollib.htmllib2 as htmllib from pysollib.kivy.LApp import LPopCommander from pysollib.kivy.LApp import LScrollView @@ -84,9 +84,9 @@ def cmp2(a, b): return (a > b) - (a < b) -class tkHTMLWriter(formatter.NullWriter): +class tkHTMLWriter(pysollib.formatter.NullWriter): def __init__(self, text, viewer, app): - formatter.NullWriter.__init__(self) + pysollib.formatter.NullWriter.__init__(self) self.text = text self.viewer = viewer @@ -630,7 +630,7 @@ def display(self, url, add=1, relpath=1, xview=0, yview=0): # self.images = {} self.text.textbuffer = '' writer = tkHTMLWriter(self.text, self, self.app) - fmt = formatter.AbstractFormatter(writer) + fmt = pysollib.formatter.AbstractFormatter(writer) parser = tkHTMLParser(fmt) parser.feed(data) parser.close() diff --git a/pysollib/pysolgtk/tkhtml.py b/pysollib/pysolgtk/tkhtml.py index 798efda2..f1c18659 100644 --- a/pysollib/pysolgtk/tkhtml.py +++ b/pysollib/pysolgtk/tkhtml.py @@ -21,7 +21,6 @@ # # --------------------------------------------------------------------------- -import formatter import htmllib import os import sys @@ -34,6 +33,7 @@ import pango +import pysollib.formatter from pysollib.mfxutil import Struct, openURL from pysollib.mygettext import _ from pysollib.settings import TITLE @@ -55,9 +55,9 @@ # * # ************************************************************************ -class tkHTMLWriter(formatter.NullWriter): +class tkHTMLWriter(pysollib.formatter.NullWriter): def __init__(self, text, viewer, app): - formatter.NullWriter.__init__(self) + pysollib.formatter.NullWriter.__init__(self) self.text = text # gtk.TextBuffer self.viewer = viewer # HTMLViewer @@ -489,7 +489,7 @@ def display(self, url, add=1, relpath=1, position=(0, 0)): self.textbuffer.delete(start, end) writer = tkHTMLWriter(self.textbuffer, self, self.app) - fmt = formatter.AbstractFormatter(writer) + fmt = pysollib.formatter.AbstractFormatter(writer) parser = tkHTMLParser(fmt) parser.feed(data) parser.close() diff --git a/pysollib/ui/tktile/tkhtml.py b/pysollib/ui/tktile/tkhtml.py index acab5711..afcb7257 100644 --- a/pysollib/ui/tktile/tkhtml.py +++ b/pysollib/ui/tktile/tkhtml.py @@ -21,10 +21,10 @@ # # --------------------------------------------------------------------------- -import formatter import os import sys +import pysollib.formatter import pysollib.htmllib2 as htmllib from pysollib.mfxutil import openURL from pysollib.mygettext import _ @@ -40,9 +40,9 @@ # ************************************************************************ -class tkHTMLWriter(formatter.NullWriter): +class tkHTMLWriter(pysollib.formatter.NullWriter): def __init__(self, text, viewer, app): - formatter.NullWriter.__init__(self) + pysollib.formatter.NullWriter.__init__(self) self.text = text self.viewer = viewer @@ -379,7 +379,7 @@ def display(self, url, add=1, relpath=1, xview=0, yview=0): self.text.delete("1.0", "end") # self.images = {} writer = tkHTMLWriter(self.text, self, self.app) - fmt = formatter.AbstractFormatter(writer) + fmt = pysollib.formatter.AbstractFormatter(writer) parser = tkHTMLParser(fmt) parser.feed(data) parser.close()