Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add to_xlsx function #262

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,12 @@ eval*
.pytest_cache
.tox
docs_api

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
4 changes: 2 additions & 2 deletions koala/Cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from koala.Range import RangeCore
from koala.utils import *

from openpyxl.compat import unicode

#from openpyxl.compat import unicode
unicode = str

class Cell(CellBase):
ctr = 0
Expand Down
4 changes: 2 additions & 2 deletions koala/Range.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from koala.ExcelError import ErrorCodes, ExcelError
from koala.utils import *

from openpyxl.compat import unicode

#from openpyxl.compat import unicode
unicode = str

# WARNING: Range should never be imported directly. Import Range from excelutils instead.

Expand Down
25 changes: 21 additions & 4 deletions koala/Spreadsheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
from koala.reader import read_archive, read_named_ranges, read_cells
# This import equivalent functions defined in Excel.
from koala.excellib import *

from openpyxl.formula.translate import Translator
from openpyxl import Workbook

from koala.serializer import *
from koala.tokenizer import reverse_rpn
from koala.utils import *
Expand All @@ -17,8 +20,10 @@
import networkx
from networkx.readwrite import json_graph

from openpyxl.compat import unicode
import koala.ToExcel

#from openpyxl.compat import unicode
unicode = str

class Spreadsheet(object):
def __init__(self, file=None, ignore_sheets=[], ignore_hidden=False, debug=False):
Expand Down Expand Up @@ -181,7 +186,7 @@ def gen_graph(self, outputs=[], inputs=[]):
sp = Spreadsheet()
sp.build_spreadsheet(G, cellmap, self.named_ranges, pointers = self.pointers, outputs = outputs, inputs = inputs, debug = self.debug)
return sp

def build_spreadsheet(self, G, cellmap, named_ranges, pointers = set(), outputs = set(), inputs = set(), debug = False):
"""
Writes the elements created by gen_graph to the object
Expand Down Expand Up @@ -274,8 +279,16 @@ def cell_add(self, address=None, cell=None, value=None, formula=None):

cellmap, G = graph_from_seeds([cell], self)

self.cellmap = cellmap
self.G = G
'''
update this spreadsheet object
'''
self.build_spreadsheet(G, cellmap, self.named_range, debug=self.debug)

'''
superceded by above
'''
# self.cellmap = cellmap
# self.G = G

print("Graph construction updated, %s nodes, %s edges, %s cellmap entries" % (len(G.nodes()),len(G.edges()),len(cellmap)))

Expand Down Expand Up @@ -663,6 +676,10 @@ def dump_json(self, fname):
def dump(self, fname):
dump(self, fname)


def to_excel(self, fname = None):
koala.ToExcel.to_excel(self, fname)

@staticmethod
def load(fname):
spreadsheet = Spreadsheet()
Expand Down
141 changes: 141 additions & 0 deletions koala/ToExcel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import openpyxl

def to_excel(spreadsheet, fname=None):
'''chaz's thing'''

if fname is None:
raise Exception('No filename specified. Please provide one.')
else:
fname=fname.split('.')[0]

# TODO:
# sort sheets before creating
# test ad-hoc worksheets
# pivot tables? eek.

spreadsheet.prune_graph()
#spreadsheet.clean_pointer()

'''
isolate sheets, cells, and formulae from graph into a dict:
{thisSheet: [(thisCell, thisFormula), (thisCell_1, thisFormula_1),...]}
do not include range names as keys
'''

theDict={}

#print(spreadsheet.addr_to_name)
#print(spreadsheet.addr_to_range)
#print(spreadsheet.named_ranges)

print('reading tree contents...')
print('address, formula, value')

for c in list(spreadsheet.cellmap.values()):
print(c.address(), c.formula, c.value)
'''
actual name ranges (as opposed to named cells (single-cell ranges)) should be excluded
from theDict
'''
if not any(c.address() in val for val in spreadsheet.addr_to_range.values()):
#print(c.address(), c.formula, c. value)

thisCell = None

thisAddress = c.address().split('!')
thisSheet = thisAddress[0]
if len(thisAddress) > 1:
thisCell = thisAddress[1]

#thisCell=None
# thisSheet = c.address().split('!')[0]
# if len(c.address().)

thisFormula = c.formula
if thisFormula is not None and thisFormula.find('=') != 0: thisFormula = '=' + thisFormula

thisValue = c.value

if thisFormula is not None:
thisValue = thisFormula

print('collecting ' + thisSheet, thisCell, thisValue )
if thisSheet not in theDict:
theDict[thisSheet] = [(thisCell, thisValue)]
else:
theDict[thisSheet].append((thisCell, thisValue))

'''
clean up dict by removing range names from keys (keys are spreadsheet names)
'''
for i in spreadsheet.named_ranges:
if i in theDict:
print(' removing name range for special handling')
theDict.pop(i)

# print('------the dict------')
# print(theDict)
# print('--------------------')

'''
create the workbook with openpyxl
'''
wb = openpyxl.Workbook()

'''
create sheets from theDict
'''
for sheetName in theDict.keys():
print(' creating sheet... ', sheetName)
wb.create_sheet(sheetName)

'''
get rid of the sheet autocreated by openpyxl
and don't consider range names (if any) as sheet named_ranges
'''
for sheet in wb.sheetnames:
if sheet not in theDict.keys() or sheet in spreadsheet.named_ranges:
rm_sheet = wb[sheet]
wb.remove(rm_sheet)

'''
add formuale to cells by sheets
'''
for sheetName, values in theDict.items():
print('adding cell contents...')
for cells in values:
thisCell = cells[0]
thisFormula = cells[1]
print(' adding ' + sheetName + '!' + thisCell, thisFormula)
wb[sheetName][thisCell] = thisFormula

'''
add named ranges
'''
print('adding ranges')
for thisName, thisAddress in spreadsheet.named_ranges.items():
thisAddress = absolute_addr(thisAddress)

print(' adding range ', thisName, thisAddress)
theRange = openpyxl.defined_name.DefinedName(thisName, attr_text=thisAddress)
wb.defined_names.append(theRange)

print('saving wb')
try:
wb.close()
wb.save(fname + '.xlsx')
except Exception as e:
print('error saving wb: "' + str(e)+'"')

def absolute_addr(theAddress):
#make the address absolute
theSheet = theAddress.split('!')[0]
theCells = theAddress.split('!')[1:]
for cell in theCells:
absCell = ''
for i in cell.split(':'):
absCell = absCell + openpyxl.cell.absolute_coordinate(i.strip())
if len(cell.split(':')) > 1: absCell = absCell + ':'
if absCell[-1] == ':': absCell = absCell[:-1]
absAddress = theSheet + '!' + absCell
return absAddress
4 changes: 3 additions & 1 deletion koala/ast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

import networkx
from networkx.classes.digraph import DiGraph
from openpyxl.compat import unicode

#from openpyxl.compat import unicode
unicode = str

from koala.utils import uniqueify, flatten, max_dimension, col2num, resolve_range
from koala.Cell import Cell
Expand Down
5 changes: 3 additions & 2 deletions koala/ast/astnodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

from networkx import NetworkXError

from openpyxl.compat import unicode
#from openpyxl.compat import unicode
unicode = str

from koala.excellib import FUNCTION_MAP, IND_FUN
from koala.utils import is_range, split_range, split_address, resolve_range
Expand Down Expand Up @@ -45,7 +46,7 @@ def __getattr__(self, name):
def children(self, ast):
try:
args = ast.predecessors(self)
args = sorted(args, key=lambda x: ast.node[x]['pos'])
args = sorted(args, key=lambda x: ast.nodes[x]['pos'])
except NetworkXError:
args = ''
return args
Expand Down
3 changes: 2 additions & 1 deletion koala/excellib.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
from calendar import monthrange
from dateutil.relativedelta import relativedelta

from openpyxl.compat import unicode
#from openpyxl.compat import unicode
unicode = str

from koala.utils import *
from koala.Range import RangeCore as Range
Expand Down
16 changes: 8 additions & 8 deletions koala/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from networkx.classes.digraph import DiGraph
from networkx.readwrite import json_graph
from networkx.drawing.nx_pydot import write_dot
from openpyxl.compat import unicode

#from openpyxl.compat import unicode
unicode = str
from koala.Cell import Cell
from koala.Range import RangeCore, RangeFactory

Expand Down Expand Up @@ -120,20 +120,20 @@ def to_float(string):
inputs = None
named_ranges = {}
infile = gzip.GzipFile(fname, 'rb')

for line in infile.read().splitlines():

line= line.decode("utf-8")

if line == "====":
if line == "====":
mode = "node0"
continue
if line == "-----":
cellmap_temp = {n.address(): n for n in nodes}
Range = RangeFactory(cellmap_temp)
mode = "node0"
continue
elif line == "edges":
elif line == "edges":
cellmap = {n.address(): n for n in nodes}
mode = "edges"
continue
Expand All @@ -148,15 +148,15 @@ def to_float(string):
continue

if mode == "node0":
[address, formula, python_expression, is_range, is_named_range, is_pointer, should_eval] = line.split(SEP)
[address, formula, python_expression, is_range, is_named_range, is_pointer, should_eval] = line.split(SEP)
formula = clean_bool(formula)
python_expression = clean_bool(python_expression)
is_range = to_bool(is_range)
is_named_range = to_bool(is_named_range)
is_pointer = to_bool(is_pointer)
should_eval = should_eval
should_eval = should_eval
mode = "node1"
elif mode == "node1":
elif mode == "node1":
if is_range:
reference = json.loads(line) if is_pointer else line # in order to be able to parse dicts
vv = Range(reference)
Expand Down
3 changes: 2 additions & 1 deletion koala/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from six import string_types
from copy import deepcopy

from openpyxl.compat import unicode
#from openpyxl.compat import unicode
unicode = str

from .ExcelError import ExcelError

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ networkx==2.1
openpyxl==2.5.3
numpy>=1.14.2
Cython==0.28.2
lxml==4.1.1
six==1.11.0
scipy>=1.0.0
python-dateutil==2.8.0
backports.functools_lru_cache==1.5
lxml==4.1.1