曾与蒿藜同雨露,한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.终随松柏到冰霜.かつては雑草やヨモギと共に雨や露を分かち合っていたが、今では松やヒノキと共に霜や雪に耐えている。曾与蒿藜同雨露,Once sharing rain and dew with weeds and wormwood, now enduring frost and snow with pines and cypresses.终随松柏到冰霜.曾与蒿藜同雨露한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.,终随松柏到冰霜.譖セ荳手珍阯懷酔髮ィ髴イ�檎サ磯囂譚セ譟丞芦蜀ー髴�曾与蒿藜同雨露,鏇句笌钂胯棞鍚岄洦闇诧紝缁堥殢鏉炬煆鍒板啺闇�终随松柏到冰霜.曾与蒿藜同雨露,한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.终随松柏到冰霜.曾与蒿藜同雨露,终随松柏到冰霜.
#! /opt/alt/python36/bin/python3.6
"""
SS1 -- a spreadsheet-like application.
"""
import os
import re
import sys
from xml.parsers import expat
from xml.sax.saxutils import escape
LEFT, CENTER, RIGHT = "LEFT", "CENTER", "RIGHT"
def ljust(x, n):
return x.ljust(n)
def center(x, n):
return x.center(n)
def rjust(x, n):
return x.rjust(n)
align2action = {LEFT: ljust, CENTER: center, RIGHT: rjust}
align2xml = {LEFT: "left", CENTER: "center", RIGHT: "right"}
xml2align = {"left": LEFT, "center": CENTER, "right": RIGHT}
align2anchor = {LEFT: "w", CENTER: "center", RIGHT: "e"}
def sum(seq):
total = 0
for x in seq:
if x is not None:
total += x
return total
class Sheet:
def __init__(self):
self.cells = {} # {(x, y): cell, ...}
self.ns = dict(
cell = self.cellvalue,
cells = self.multicellvalue,
sum = sum,
)
def cellvalue(self, x, y):
cell = self.getcell(x, y)
if hasattr(cell, 'recalc'):
return cell.recalc(self.ns)
else:
return cell
def multicellvalue(self, x1, y1, x2, y2):
if x1 > x2:
x1, x2 = x2, x1
if y1 > y2:
y1, y2 = y2, y1
seq = []
for y in range(y1, y2+1):
for x in range(x1, x2+1):
seq.append(self.cellvalue(x, y))
return seq
def getcell(self, x, y):
return self.cells.get((x, y))
def setcell(self, x, y, cell):
assert x > 0 and y > 0
assert isinstance(cell, BaseCell)
self.cells[x, y] = cell
def clearcell(self, x, y):
try:
del self.cells[x, y]
except KeyError:
pass
def clearcells(self, x1, y1, x2, y2):
for xy in self.selectcells(x1, y1, x2, y2):
del self.cells[xy]
def clearrows(self, y1, y2):
self.clearcells(0, y1, sys.maxsize, y2)
def clearcolumns(self, x1, x2):
self.clearcells(x1, 0, x2, sys.maxsize)
def selectcells(self, x1, y1, x2, y2):
if x1 > x2:
x1, x2 = x2, x1
if y1 > y2:
y1, y2 = y2, y1
return [(x, y) for x, y in self.cells
if x1 <= x <= x2 and y1 <= y <= y2]
def movecells(self, x1, y1, x2, y2, dx, dy):
if dx == 0 and dy == 0:
return
if x1 > x2:
x1, x2 = x2, x1
if y1 > y2:
y1, y2 = y2, y1
assert x1+dx > 0 and y1+dy > 0
new = {}
for x, y in self.cells:
cell = self.cells[x, y]
if hasattr(cell, 'renumber'):
cell = cell.renumber(x1, y1, x2, y2, dx, dy)
if x1 <= x <= x2 and y1 <= y <= y2:
x += dx
y += dy
new[x, y] = cell
self.cells = new
def insertrows(self, y, n):
assert n > 0
self.movecells(0, y, sys.maxsize, sys.maxsize, 0, n)
def deleterows(self, y1, y2):
if y1 > y2:
y1, y2 = y2, y1
self.clearrows(y1, y2)
self.movecells(0, y2+1, sys.maxsize, sys.maxsize, 0, y1-y2-1)
def insertcolumns(self, x, n):
assert n > 0
self.movecells(x, 0, sys.maxsize, sys.maxsize, n, 0)
def deletecolumns(self, x1, x2):
if x1 > x2:
x1, x2 = x2, x1
self.clearcells(x1, x2)
self.movecells(x2+1, 0, sys.maxsize, sys.maxsize, x1-x2-1, 0)
def getsize(self):
maxx = maxy = 0
for x, y in self.cells:
maxx = max(maxx, x)
maxy = max(maxy, y)
return maxx, maxy
def reset(self):
for cell in self.cells.values():
if hasattr(cell, 'reset'):
cell.reset()
def recalc(self):
self.reset()
for cell in self.cells.values():
if hasattr(cell, 'recalc'):
cell.recalc(self.ns)
def display(self):
maxx, maxy = self.getsize()
width, height = maxx+1, maxy+1
colwidth = [1] * width
full = {}
# Add column heading labels in row 0
for x in range(1, width):
full[x, 0] = text, alignment = colnum2name(x), RIGHT
colwidth[x] = max(colwidth[x], len(text))
# Add row labels in column 0
for y in range(1, height):
full[0, y] = text, alignment = str(y), RIGHT
colwidth[0] = max(colwidth[0], len(text))
# Add sheet cells in columns with x>0 and y>0
for (x, y), cell in self.cells.items():
if x <= 0 or y <= 0:
continue
if hasattr(cell, 'recalc'):
cell.recalc(self.ns)
if hasattr(cell, 'format'):
text, alignment = cell.format()
assert isinstance(text, str)
assert alignment in (LEFT, CENTER, RIGHT)
else:
text = str(cell)
if isinstance(cell, str):
alignment = LEFT
else:
alignment = RIGHT
full[x, y] = (text, alignment)
colwidth[x] = max(colwidth[x], len(text))
# Calculate the horizontal separator line (dashes and dots)
sep = ""
for x in range(width):
if sep:
sep += "+"
sep += "-"*colwidth[x]
# Now print The full grid
for y in range(height):
line = ""
for x in range(width):
text, alignment = full.get((x, y)) or ("", LEFT)
text = align2action[alignment](text, colwidth[x])
if line:
line += '|'
line += text
print(line)
if y == 0:
print(sep)
def xml(self):
out = ['<spreadsheet>']
for (x, y), cell in self.cells.items():
if hasattr(cell, 'xml'):
cellxml = cell.xml()
else:
cellxml = '<value>%s</value>' % escape(cell)
out.append('<cell row="%s" col="%s">\n %s\n</cell>' %
(y, x, cellxml))
out.append('</spreadsheet>')
return '\n'.join(out)
def save(self, filename):
text = self.xml()
with open(filename, "w", encoding='utf-8') as f:
f.write(text)
if text and not text.endswith('\n'):
f.write('\n')
def load(self, filename):
with open(filename, 'rb') as f:
SheetParser(self).parsefile(f)
class SheetParser:
def __init__(self, sheet):
self.sheet = sheet
def parsefile(self, f):
parser = expat.ParserCreate()
parser.StartElementHandler = self.startelement
parser.EndElementHandler = self.endelement
parser.CharacterDataHandler = self.data
parser.ParseFile(f)
def startelement(self, tag, attrs):
method = getattr(self, 'start_'+tag, None)
if method:
method(attrs)
self.texts = []
def data(self, text):
self.texts.append(text)
def endelement(self, tag):
method = getattr(self, 'end_'+tag, None)
if method:
method("".join(self.texts))
def start_cell(self, attrs):
self.y = int(attrs.get("row"))
self.x = int(attrs.get("col"))
def start_value(self, attrs):
self.fmt = attrs.get('format')
self.alignment = xml2align.get(attrs.get('align'))
start_formula = start_value
def end_int(self, text):
try:
self.value = int(text)
except (TypeError, ValueError):
self.value = None
end_long = end_int
def end_double(self, text):
try:
self.value = float(text)
except (TypeError, ValueError):
self.value = None
def end_complex(self, text):
try:
self.value = complex(text)
except (TypeError, ValueError):
self.value = None
def end_string(self, text):
self.value = text
def end_value(self, text):
if isinstance(self.value, BaseCell):
self.cell = self.value
elif isinstance(self.value, str):
self.cell = StringCell(self.value,
self.fmt or "%s",
self.alignment or LEFT)
else:
self.cell = NumericCell(self.value,
self.fmt or "%s",
self.alignment or RIGHT)
def end_formula(self, text):
self.cell = FormulaCell(text,
self.fmt or "%s",
self.alignment or RIGHT)
def end_cell(self, text):
self.sheet.setcell(self.x, self.y, self.cell)
class BaseCell:
__init__ = None # Must provide
"""Abstract base class for sheet cells.
Subclasses may but needn't provide the following APIs:
cell.reset() -- prepare for recalculation
cell.recalc(ns) -> value -- recalculate formula
cell.format() -> (value, alignment) -- return formatted value
cell.xml() -> string -- return XML
"""
class NumericCell(BaseCell):
def __init__(self, value, fmt="%s", alignment=RIGHT):
assert isinstance(value, (int, float, complex))
assert alignment in (LEFT, CENTER, RIGHT)
self.value = value
self.fmt = fmt
self.alignment = alignment
def recalc(self, ns):
return self.value
def format(self):
try:
text = self.fmt % self.value
except:
text = str(self.value)
return text, self.alignment
def xml(self):
method = getattr(self, '_xml_' + type(self.value).__name__)
return '<value align="%s" format="%s">%s</value>' % (
align2xml[self.alignment],
self.fmt,
method())
def _xml_int(self):
if -2**31 <= self.value < 2**31:
return '<int>%s</int>' % self.value
else:
return '<long>%s</long>' % self.value
def _xml_float(self):
return '<double>%r</double>' % self.value
def _xml_complex(self):
return '<complex>%r</complex>' % self.value
class StringCell(BaseCell):
def __init__(self, text, fmt="%s", alignment=LEFT):
assert isinstance(text, str)
assert alignment in (LEFT, CENTER, RIGHT)
self.text = text
self.fmt = fmt
self.alignment = alignment
def recalc(self, ns):
return self.text
def format(self):
return self.text, self.alignment
def xml(self):
s = '<value align="%s" format="%s"><string>%s</string></value>'
return s % (
align2xml[self.alignment],
self.fmt,
escape(self.text))
class FormulaCell(BaseCell):
def __init__(self, formula, fmt="%s", alignment=RIGHT):
assert alignment in (LEFT, CENTER, RIGHT)
self.formula = formula
self.translated = translate(self.formula)
self.fmt = fmt
self.alignment = alignment
self.reset()
def reset(self):
self.value = None
def recalc(self, ns):
if self.value is None:
try:
self.value = eval(self.translated, ns)
except:
exc = sys.exc_info()[0]
if hasattr(exc, "__name__"):
self.value = exc.__name__
else:
self.value = str(exc)
return self.value
def format(self):
try:
text = self.fmt % self.value
except:
text = str(self.value)
return text, self.alignment
def xml(self):
return '<formula align="%s" format="%s">%s</formula>' % (
align2xml[self.alignment],
self.fmt,
escape(self.formula))
def renumber(self, x1, y1, x2, y2, dx, dy):
out = []
for part in re.split(r'(\w+)', self.formula):
m = re.match('^([A-Z]+)([1-9][0-9]*)$', part)
if m is not None:
sx, sy = m.groups()
x = colname2num(sx)
y = int(sy)
if x1 <= x <= x2 and y1 <= y <= y2:
part = cellname(x+dx, y+dy)
out.append(part)
return FormulaCell("".join(out), self.fmt, self.alignment)
def translate(formula):
"""Translate a formula containing fancy cell names to valid Python code.
Examples:
B4 -> cell(2, 4)
B4:Z100 -> cells(2, 4, 26, 100)
"""
out = []
for part in re.split(r"(\w+(?::\w+)?)", formula):
m = re.match(r"^([A-Z]+)([1-9][0-9]*)(?::([A-Z]+)([1-9][0-9]*))?$", part)
if m is None:
out.append(part)
else:
x1, y1, x2, y2 = m.groups()
x1 = colname2num(x1)
if x2 is None:
s = "cell(%s, %s)" % (x1, y1)
else:
x2 = colname2num(x2)
s = "cells(%s, %s, %s, %s)" % (x1, y1, x2, y2)
out.append(s)
return "".join(out)
def cellname(x, y):
"Translate a cell coordinate to a fancy cell name (e.g. (1, 1)->'A1')."
assert x > 0 # Column 0 has an empty name, so can't use that
return colnum2name(x) + str(y)
def colname2num(s):
"Translate a column name to number (e.g. 'A'->1, 'Z'->26, 'AA'->27)."
s = s.upper()
n = 0
for c in s:
assert 'A' <= c <= 'Z'
n = n*26 + ord(c) - ord('A') + 1
return n
def colnum2name(n):
"Translate a column number to name (e.g. 1->'A', etc.)."
assert n > 0
s = ""
while n:
n, m = divmod(n-1, 26)
s = chr(m+ord('A')) + s
return s
import tkinter as Tk
class SheetGUI:
"""Beginnings of a GUI for a spreadsheet.
TO DO:
- clear multiple cells
- Insert, clear, remove rows or columns
- Show new contents while typing
- Scroll bars
- Grow grid when window is grown
- Proper menus
- Undo, redo
- Cut, copy and paste
- Formatting and alignment
"""
def __init__(self, filename="sheet1.xml", rows=10, columns=5):
"""Constructor.
Load the sheet from the filename argument.
Set up the Tk widget tree.
"""
# Create and load the sheet
self.filename = filename
self.sheet = Sheet()
if os.path.isfile(filename):
self.sheet.load(filename)
# Calculate the needed grid size
maxx, maxy = self.sheet.getsize()
rows = max(rows, maxy)
columns = max(columns, maxx)
# Create the widgets
self.root = Tk.Tk()
self.root.wm_title("Spreadsheet: %s" % self.filename)
self.beacon = Tk.Label(self.root, text="A1",
font=('helvetica', 16, 'bold'))
self.entry = Tk.Entry(self.root)
self.savebutton = Tk.Button(self.root, text="Save",
command=self.save)
self.cellgrid = Tk.Frame(self.root)
# Configure the widget lay-out
self.cellgrid.pack(side="bottom", expand=1, fill="both")
self.beacon.pack(side="left")
self.savebutton.pack(side="right")
self.entry.pack(side="left", expand=1, fill="x")
# Bind some events
self.entry.bind("<Return>", self.return_event)
self.entry.bind("<Shift-Return>", self.shift_return_event)
self.entry.bind("<Tab>", self.tab_event)
self.entry.bind("<Shift-Tab>", self.shift_tab_event)
self.entry.bind("<Delete>", self.delete_event)
self.entry.bind("<Escape>", self.escape_event)
# Now create the cell grid
self.makegrid(rows, columns)
# Select the top-left cell
self.currentxy = None
self.cornerxy = None
self.setcurrent(1, 1)
# Copy the sheet cells to the GUI cells
self.sync()
def delete_event(self, event):
if self.cornerxy != self.currentxy and self.cornerxy is not None:
self.sheet.clearcells(*(self.currentxy + self.cornerxy))
else:
self.sheet.clearcell(*self.currentxy)
self.sync()
self.entry.delete(0, 'end')
return "break"
def escape_event(self, event):
x, y = self.currentxy
self.load_entry(x, y)
def load_entry(self, x, y):
cell = self.sheet.getcell(x, y)
if cell is None:
text = ""
elif isinstance(cell, FormulaCell):
text = '=' + cell.formula
else:
text, alignment = cell.format()
self.entry.delete(0, 'end')
self.entry.insert(0, text)
self.entry.selection_range(0, 'end')
def makegrid(self, rows, columns):
"""Helper to create the grid of GUI cells.
The edge (x==0 or y==0) is filled with labels; the rest is real cells.
"""
self.rows = rows
self.columns = columns
self.gridcells = {}
# Create the top left corner cell (which selects all)
cell = Tk.Label(self.cellgrid, relief='raised')
cell.grid_configure(column=0, row=0, sticky='NSWE')
cell.bind("<ButtonPress-1>", self.selectall)
# Create the top row of labels, and configure the grid columns
for x in range(1, columns+1):
self.cellgrid.grid_columnconfigure(x, minsize=64)
cell = Tk.Label(self.cellgrid, text=colnum2name(x), relief='raised')
cell.grid_configure(column=x, row=0, sticky='WE')
self.gridcells[x, 0] = cell
cell.__x = x
cell.__y = 0
cell.bind("<ButtonPress-1>", self.selectcolumn)
cell.bind("<B1-Motion>", self.extendcolumn)
cell.bind("<ButtonRelease-1>", self.extendcolumn)
cell.bind("<Shift-Button-1>", self.extendcolumn)
# Create the leftmost column of labels
for y in range(1, rows+1):
cell = Tk.Label(self.cellgrid, text=str(y), relief='raised')
cell.grid_configure(column=0, row=y, sticky='WE')
self.gridcells[0, y] = cell
cell.__x = 0
cell.__y = y
cell.bind("<ButtonPress-1>", self.selectrow)
cell.bind("<B1-Motion>", self.extendrow)
cell.bind("<ButtonRelease-1>", self.extendrow)
cell.bind("<Shift-Button-1>", self.extendrow)
# Create the real cells
for x in range(1, columns+1):
for y in range(1, rows+1):
cell = Tk.Label(self.cellgrid, relief='sunken',
bg='white', fg='black')
cell.grid_configure(column=x, row=y, sticky='NSWE')
self.gridcells[x, y] = cell
cell.__x = x
cell.__y = y
# Bind mouse events
cell.bind("<ButtonPress-1>", self.press)
cell.bind("<B1-Motion>", self.motion)
cell.bind("<ButtonRelease-1>", self.release)
cell.bind("<Shift-Button-1>", self.release)
def selectall(self, event):
self.setcurrent(1, 1)
self.setcorner(sys.maxsize, sys.maxsize)
def selectcolumn(self, event):
x, y = self.whichxy(event)
self.setcurrent(x, 1)
self.setcorner(x, sys.maxsize)
def extendcolumn(self, event):
x, y = self.whichxy(event)
if x > 0:
self.setcurrent(self.currentxy[0], 1)
self.setcorner(x, sys.maxsize)
def selectrow(self, event):
x, y = self.whichxy(event)
self.setcurrent(1, y)
self.setcorner(sys.maxsize, y)
def extendrow(self, event):
x, y = self.whichxy(event)
if y > 0:
self.setcurrent(1, self.currentxy[1])
self.setcorner(sys.maxsize, y)
def press(self, event):
x, y = self.whichxy(event)
if x > 0 and y > 0:
self.setcurrent(x, y)
def motion(self, event):
x, y = self.whichxy(event)
if x > 0 and y > 0:
self.setcorner(x, y)
release = motion
def whichxy(self, event):
w = self.cellgrid.winfo_containing(event.x_root, event.y_root)
if w is not None and isinstance(w, Tk.Label):
try:
return w.__x, w.__y
except AttributeError:
pass
return 0, 0
def save(self):
self.sheet.save(self.filename)
def setcurrent(self, x, y):
"Make (x, y) the current cell."
if self.currentxy is not None:
self.change_cell()
self.clearfocus()
self.beacon['text'] = cellname(x, y)
self.load_entry(x, y)
self.entry.focus_set()
self.currentxy = x, y
self.cornerxy = None
gridcell = self.gridcells.get(self.currentxy)
if gridcell is not None:
gridcell['bg'] = 'yellow'
def setcorner(self, x, y):
if self.currentxy is None or self.currentxy == (x, y):
self.setcurrent(x, y)
return
self.clearfocus()
self.cornerxy = x, y
x1, y1 = self.currentxy
x2, y2 = self.cornerxy or self.currentxy
if x1 > x2:
x1, x2 = x2, x1
if y1 > y2:
y1, y2 = y2, y1
for (x, y), cell in self.gridcells.items():
if x1 <= x <= x2 and y1 <= y <= y2:
cell['bg'] = 'lightBlue'
gridcell = self.gridcells.get(self.currentxy)
if gridcell is not None:
gridcell['bg'] = 'yellow'
self.setbeacon(x1, y1, x2, y2)
def setbeacon(self, x1, y1, x2, y2):
if x1 == y1 == 1 and x2 == y2 == sys.maxsize:
name = ":"
elif (x1, x2) == (1, sys.maxsize):
if y1 == y2:
name = "%d" % y1
else:
name = "%d:%d" % (y1, y2)
elif (y1, y2) == (1, sys.maxsize):
if x1 == x2:
name = "%s" % colnum2name(x1)
else:
name = "%s:%s" % (colnum2name(x1), colnum2name(x2))
else:
name1 = cellname(*self.currentxy)
name2 = cellname(*self.cornerxy)
name = "%s:%s" % (name1, name2)
self.beacon['text'] = name
def clearfocus(self):
if self.currentxy is not None:
x1, y1 = self.currentxy
x2, y2 = self.cornerxy or self.currentxy
if x1 > x2:
x1, x2 = x2, x1
if y1 > y2:
y1, y2 = y2, y1
for (x, y), cell in self.gridcells.items():
if x1 <= x <= x2 and y1 <= y <= y2:
cell['bg'] = 'white'
def return_event(self, event):
"Callback for the Return key."
self.change_cell()
x, y = self.currentxy
self.setcurrent(x, y+1)
return "break"
def shift_return_event(self, event):
"Callback for the Return key with Shift modifier."
self.change_cell()
x, y = self.currentxy
self.setcurrent(x, max(1, y-1))
return "break"
def tab_event(self, event):
"Callback for the Tab key."
self.change_cell()
x, y = self.currentxy
self.setcurrent(x+1, y)
return "break"
def shift_tab_event(self, event):
"Callback for the Tab key with Shift modifier."
self.change_cell()
x, y = self.currentxy
self.setcurrent(max(1, x-1), y)
return "break"
def change_cell(self):
"Set the current cell from the entry widget."
x, y = self.currentxy
text = self.entry.get()
cell = None
if text.startswith('='):
cell = FormulaCell(text[1:])
else:
for cls in int, float, complex:
try:
value = cls(text)
except (TypeError, ValueError):
continue
else:
cell = NumericCell(value)
break
if cell is None and text:
cell = StringCell(text)
if cell is None:
self.sheet.clearcell(x, y)
else:
self.sheet.setcell(x, y, cell)
self.sync()
def sync(self):
"Fill the GUI cells from the sheet cells."
self.sheet.recalc()
for (x, y), gridcell in self.gridcells.items():
if x == 0 or y == 0:
continue
cell = self.sheet.getcell(x, y)
if cell is None:
gridcell['text'] = ""
else:
if hasattr(cell, 'format'):
text, alignment = cell.format()
else:
text, alignment = str(cell), LEFT
gridcell['text'] = text
gridcell['anchor'] = align2anchor[alignment]
def test_basic():
"Basic non-gui self-test."
a = Sheet()
for x in range(1, 11):
for y in range(1, 11):
if x == 1:
cell = NumericCell(y)
elif y == 1:
cell = NumericCell(x)
else:
c1 = cellname(x, 1)
c2 = cellname(1, y)
formula = "%s*%s" % (c1, c2)
cell = FormulaCell(formula)
a.setcell(x, y, cell)
## if os.path.isfile("sheet1.xml"):
## print "Loading from sheet1.xml"
## a.load("sheet1.xml")
a.display()
a.save("sheet1.xml")
def test_gui():
"GUI test."
if sys.argv[1:]:
filename = sys.argv[1]
else:
filename = "sheet1.xml"
g = SheetGUI(filename)
g.root.mainloop()
if __name__ == '__main__':
#test_basic()
test_gui()