Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (144 page)

BOOK: Programming Python
13.58Mb size Format: txt, pdf, ePub
ads
popuputil: General-Purpose GUI Pop Ups

Example 14-6
implements
a handful of utility pop-up windows in a module, in case
they ever prove useful in other programs. Note that the same windows
utility module is imported here, to give a common look-and-feel to the
pop ups (icons, titles, and
so
on
).

Example 14-6. PP4E\Internet\Email\PyMailGui\popuputil.py

"""
#############################################################################
utility windows - may be useful in other programs
#############################################################################
"""
from tkinter import *
from PP4E.Gui.Tools.windows import PopupWindow
class HelpPopup(PopupWindow):
"""
custom Toplevel that shows help text as scrolled text
source button runs a passed-in callback handler
3.0 alternative: use HTML file and webbrowser module
"""
myfont = 'system' # customizable
mywidth = 78 # 3.0: start width
def __init__(self, appname, helptext, iconfile=None, showsource=lambda:0):
PopupWindow.__init__(self, appname, 'Help', iconfile)
from tkinter.scrolledtext import ScrolledText # a nonmodal dialog
bar = Frame(self) # pack first=clip last
bar.pack(side=BOTTOM, fill=X)
code = Button(bar, bg='beige', text="Source", command=showsource)
quit = Button(bar, bg='beige', text="Cancel", command=self.destroy)
code.pack(pady=1, side=LEFT)
quit.pack(pady=1, side=LEFT)
text = ScrolledText(self) # add Text + scrollbar
text.config(font=self.myfont)
text.config(width=self.mywidth) # too big for showinfo
text.config(bg='steelblue', fg='white') # erase on btn or return
text.insert('0.0', helptext)
text.pack(expand=YES, fill=BOTH)
self.bind("", (lambda event: self.destroy()))
def askPasswordWindow(appname, prompt):
"""
modal dialog to input password string
getpass.getpass uses stdin, not GUI
tkSimpleDialog.askstring echos input
"""
win = PopupWindow(appname, 'Prompt') # a configured Toplevel
Label(win, text=prompt).pack(side=LEFT)
entvar = StringVar(win)
ent = Entry(win, textvariable=entvar, show='*') # display * for input
ent.pack(side=RIGHT, expand=YES, fill=X)
ent.bind('', lambda event: win.destroy())
ent.focus_set(); win.grab_set(); win.wait_window()
win.update() # update forces redraw
return entvar.get() # ent widget is now gone
class BusyBoxWait(PopupWindow):
"""
pop up blocking wait message box: thread waits
main GUI event thread stays alive during wait
but GUI is inoperable during this wait state;
uses quit redef here because lower, not leftmost;
"""
def __init__(self, appname, message):
PopupWindow.__init__(self, appname, 'Busy')
self.protocol('WM_DELETE_WINDOW', lambda:0) # ignore deletes
label = Label(self, text=message + '...') # win.quit() to erase
label.config(height=10, width=40, cursor='watch') # busy cursor
label.pack()
self.makeModal()
self.message, self.label = message, label
def makeModal(self):
self.focus_set() # grab application
self.grab_set() # wait for threadexit
def changeText(self, newtext):
self.label.config(text=self.message + ': ' + newtext)
def quit(self):
self.destroy() # don't verify quit
class BusyBoxNowait(BusyBoxWait):
"""
pop up nonblocking wait window
call changeText to show progress, quit to close
"""
def makeModal(self):
pass
if __name__ == '__main__':
HelpPopup('spam', 'See figure 1...\n')
print(askPasswordWindow('spam', 'enter password'))
input('Enter to exit') # pause if clicked
wraplines: Line Split Tools

The
module in
Example 14-7
implements general tools
for wrapping long lines, at either a fixed column or the first delimiter
at or before a fixed column. PyMailGUI uses this file’s
wrapText1
function for text in view, reply,
and forward windows, but this code is potentially useful in other
programs. Run the file as a script to watch its self-test code at work,
and study its functions to see its text-processing logic.

Example 14-7. PP4E\Internet\Email\PyMailGui\wraplines.py

"""
###############################################################################
split lines on fixed columns or at delimiters before a column;
see also: related but different textwrap standard library module (2.3+);
4E caveat: this assumes str; supporting bytes might help avoid some decodes;
###############################################################################
"""
defaultsize = 80
def wrapLinesSimple(lineslist, size=defaultsize):
"split at fixed position size"
wraplines = []
for line in lineslist:
while True:
wraplines.append(line[:size]) # OK if len < size
line = line[size:] # split without analysis
if not line: break
return wraplines
def wrapLinesSmart(lineslist, size=defaultsize, delimiters='.,:\t '):
"wrap at first delimiter left of size"
wraplines = []
for line in lineslist:
while True:
if len(line) <= size:
wraplines += [line]
break
else:
for look in range(size-1, size // 2, −1): # 3.0: need // not /
if line[look] in delimiters:
front, line = line[:look+1], line[look+1:]
break
else:
front, line = line[:size], line[size:]
wraplines += [front]
return wraplines
###############################################################################
# common use case utilities
###############################################################################
def wrapText1(text, size=defaultsize): # better for line-based txt: mail
"when text read all at once" # keeps original line brks struct
lines = text.split('\n') # split on newlines
lines = wrapLinesSmart(lines, size) # wrap lines on delimiters
return '\n'.join(lines) # put back together
def wrapText2(text, size=defaultsize): # more uniform across lines
"same, but treat as one long line" # but loses original line struct
text = text.replace('\n', ' ') # drop newlines if any
lines = wrapLinesSmart([text], size) # wrap single line on delimiters
return lines # caller puts back together
def wrapText3(text, size=defaultsize):
"same, but put back together"
lines = wrapText2(text, size) # wrap as single long line
return '\n'.join(lines) + '\n' # make one string with newlines
def wrapLines1(lines, size=defaultsize):
"when newline included at end"
lines = [line[:-1] for line in lines] # strip off newlines (or .rstrip)
lines = wrapLinesSmart(lines, size) # wrap on delimiters
return [(line + '\n') for line in lines] # put them back
def wrapLines2(lines, size=defaultsize): # more uniform across lines
"same, but concat as one long line" # but loses original structure
text = ''.join(lines) # put together as 1 line
lines = wrapText2(text) # wrap on delimiters
return [(line + '\n') for line in lines] # put newlines on ends
###############################################################################
# self-test
###############################################################################
if __name__ == '__main__':
lines = ['spam ham ' * 20 + 'spam,ni' * 20,
'spam ham ' * 20,
'spam,ni' * 20,
'spam ham.ni' * 20,
'',
'spam'*80,
' ',
'spam ham eggs']
sep = '-' * 30
print('all', sep)
for line in lines: print(repr(line))
print('simple', sep)
for line in wrapLinesSimple(lines): print(repr(line))
print('smart', sep)
for line in wrapLinesSmart(lines): print(repr(line))
print('single1', sep)
for line in wrapLinesSimple([lines[0]], 60): print(repr(line))
print('single2', sep)
for line in wrapLinesSmart([lines[0]], 60): print(repr(line))
print('combined text', sep)
for line in wrapLines2(lines): print(repr(line))
print('combined lines', sep)
print(wrapText1('\n'.join(lines)))
assert ''.join(lines) == ''.join(wrapLinesSimple(lines, 60))
assert ''.join(lines) == ''.join(wrapLinesSmart(lines, 60))
print(len(''.join(lines)), end=' ')
print(len(''.join(wrapLinesSimple(lines))), end=' ')
print(len(''.join(wrapLinesSmart(lines))), end=' ')
print(len(''.join(wrapLinesSmart(lines, 60))), end=' ')
input('Press enter') # pause if clicked
html2text: Extracting Text from HTML (Prototype, Preview)

Example 14-8
lists
the code of the simple-minded HTML parser that PyMailGUI
uses to extract plain text from mails whose main (or only) text part is
in HTML form. This extracted text is used both for display and for the
initial text in replies and forwards. Its original HTML form is also
displayed in its full glory in a popped-up web browser as before.

This is a
prototype
. Because PyMailGUI is
oriented toward plain text today, this parser is intended as a temporary
workaround until a HTML viewer/editor widget solution is found. Because
of that, this is at best a first cut which has not been polished to any
significant extent. Robustly parsing HTML in its entirety is a task well
beyond the scope of this chapter and book. When this parser fails to
render good plain text (and it will!), users can still view and
cut-and-paste the properly formatted text from the web browser.

This is also a
preview
. HTML parsing is not
covered until
Chapter 19
of this book, so
you’ll have to take this on faith until we refer back to it in that
later chapter. Unfortunately, this feature was added to PyMailGUI late
in the book project, and avoiding this forward reference didn’t seem to
justify omitting the improvement altogether. For more details on HTML
parsing, stay tuned for (or flip head to)
Chapter 19
.

In short, the class here provides handler methods that receive
callbacks from an HTML parser, as tags and their content is recognized;
we use this model here to save text we’re interested in along the way.
Besides the parser class, we could also use Python’s
html.entities
module to map more entity types
than are hardcoded here—another tool we will meet in
Chapter 19
.

Despite its limitations, this example serves as a rough guide to
help get you started, and any result it produces is certainly an
improvement upon the prior edition’s display and quoting of raw
HTML.

Example 14-8. PP4E\Internet\Email\PyMailGui\html2text.py

"""
################################################################
*VERY* preliminary html-to-text extractor, for text to be
quoted in replies and forwards, and displayed in the main
text display component. Only used when the main text part
is HTML (i.e., no alternative or other text parts to show).
We also need to know if this is HTML or not, but findMainText
already returns the main text's content type.
This is mostly provided as a first cut, to help get you started
on a more complete solution. It hasn't been polished, because
any result is better than displaying raw HTML, and it's probably
a better idea to migrate to an HTML viewer/editor widget in the
future anyhow. As is, PyMailGUI is still plain-text biased.
If (really, when) this parser fails to render well, users can
instead view and cut-and-paste from the web browser popped up
to display the HTML. See Chapter 19 for more on HTML parsing.
################################################################
"""
from html.parser import HTMLParser # Python std lib parser (sax-like model)
class Parser(HTMLParser): # subclass parser, define callback methods
def __init__(self): # text assumed to be str, any encoding ok
HTMLParser.__init__(self)
self.text = '[Extracted HTML text]'
self.save = 0
self.last = ''
def addtext(self, new):
if self.save > 0:
self.text += new
self.last = new
def addeoln(self, force=False):
if force or self.last != '\n':
self.addtext('\n')
def handle_starttag(self, tag, attrs): # + others imply content start?
if tag in ('p', 'div', 'table', 'h1', 'h2', 'li'):
self.save += 1
self.addeoln()
elif tag == 'td':
self.addeoln()
elif tag == 'style': # + others imply end of prior?
self.save -= 1
elif tag == 'br':
self.addeoln(True)
elif tag == 'a':
alts = [pair for pair in attrs if pair[0] == 'alt']
if alts:
name, value = alts[0]
self.addtext('[' + value.replace('\n', '') + ']')
def handle_endtag(self, tag):
if tag in ('p', 'div', 'table', 'h1', 'h2', 'li'):
self.save -= 1
self.addeoln()
elif tag == 'style':
self.save += 1
def handle_data(self, data):
data = data.replace('\n', '') # what about
?
data = data.replace('\t', ' ')
if data != ' ' * len(data):
self.addtext(data)
def handle_entityref(self, name):
xlate = dict(lt='<', gt='>', amp='&', nbsp='').get(name, '?')
if xlate:
self.addtext(xlate) # plus many others: show ? as is
def html2text(text):
try:
hp = Parser()
hp.feed(text)
return(hp.text)
except:
return text
if __name__ == '__main__':
# to test me: html2text.py media\html2text-test\htmlmail1.html
# parse file name in commandline, display result in tkinter Text
# file assumed to be in Unicode platform default, but text need not be
import sys, tkinter
text = open(sys.argv[1], 'r').read()
text = html2text(text)
t = tkinter.Text()
t.insert('1.0', text)
t.pack()
t.mainloop()
Note

After this example and chapter had been written and finalized, I
did a search for HTML-to-text translators on the Web to try to find
better options, and I discovered a Python-coded solution which is much
more complete and robust than the simple prototype script here.
Regrettably, I also discovered that this system is named the same as
the script
listed here
!

This was unintentional and unforeseen (alas, developers are
predisposed to think alike). For details on this more widely tested
and much better alternative, search the Web for
html2text
. It’s open source, but follows the GPL
license, and is available only for Python 2.X at this writing (e.g.,
it uses the 2.X
sgmllib
which has
been removed in favor of the new
html.parser
in 3.X). Unfortunately, its GPL
license may raise copyright concerns if shipped with PyMailGUI in this
book’s example package or otherwise; worse, its 2.X status means it
cannot be used at all with this book’s 3.X examples today.

There are additional plain-text extractor options on the Web
worth checking out, including BeautifulSoup and yet another named
html2text.py
(no, really!). They also appear to
be available for just 2.X today, though naturally, this story may
change by the time you read this note. There’s no reason to reinvent
the wheel, unless existing wheels don’t fit your cart!

BOOK: Programming Python
13.58Mb size Format: txt, pdf, ePub
ads

Other books

Taking What's Mine by Alexa Riley
For All Eternity by Heather Cullman
Breathless by Adams, Claire
Marry Me by Dan Rhodes
High Spirits at Harroweby by Comstock, Mary Chase
The Gift of Charms by Julia Suzuki
Kyn 3: Feral by Mina Carter
The Borrowed Bride by Susan Wiggs