Skip to content

Commit

Permalink
experimental python 3 support
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Ray committed Aug 14, 2018
1 parent 65744d4 commit d56d58d
Show file tree
Hide file tree
Showing 63 changed files with 13,789 additions and 0 deletions.
21 changes: 21 additions & 0 deletions mm3/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env python

__version__ = "0.1.3"
__author__ = [
"Brian Ray <[email protected]>",
]
__license__ = "TBD"

from .document_base import *
from . import config_base
from . import model_base
import logging
logging.basicConfig()


Date = model_base.DateFieldType
URL = model_base.URLFieldType
Image = model_base.ImageFieldType
Formula = model_base.FormulaFieldType

Config = config_base.ConfigBase
84 changes: 84 additions & 0 deletions mm3/color_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from .lib.font_data.decorators import memoized

excel_color_dict = {}

excel_color_dict['0, 0, 0'] = 0x08
excel_color_dict['255, 255, 255'] = 0x09
excel_color_dict['255, 0, 0'] = 0x0A
excel_color_dict['0, 255, 0'] = 0x0B
excel_color_dict['0, 0, 255'] = 0x0C
excel_color_dict['255, 255, 0'] = 0x0D
excel_color_dict['255, 0, 255'] = 0x0E
excel_color_dict['0, 255, 255'] = 0x0F
excel_color_dict['128, 0, 0'] = 0x10
excel_color_dict['0, 128, 0'] = 0x11
excel_color_dict['0, 0, 128'] = 0x12
excel_color_dict['128, 128, 0'] = 0x13
excel_color_dict['128, 0, 128'] = 0x14
excel_color_dict['0, 128, 128'] = 0x15
excel_color_dict['192, 192, 192'] = 0x16
excel_color_dict['128, 128, 128'] = 0x17
excel_color_dict['153, 153, 255'] = 0x18
excel_color_dict['153, 51, 102'] = 0x1A
excel_color_dict['255, 255, 204'] = 0x1C
excel_color_dict['204, 255, 255'] = 0x1D
excel_color_dict['102, 0, 102'] = 0x1E
excel_color_dict['255, 128, 128'] = 0x1F
excel_color_dict['0, 102, 204'] = 0x28
excel_color_dict['204, 204, 255'] = 0x29
excel_color_dict['0, 204, 255'] = 0x2A
excel_color_dict['204, 255, 204'] = 0x2B
excel_color_dict['255, 255, 153'] = 0x2C
excel_color_dict['153, 204, 255'] = 0x2D
excel_color_dict['255, 153, 204'] = 0x2E
excel_color_dict['204, 153, 255'] = 0x2F
excel_color_dict['255, 204, 153'] = 0x30
excel_color_dict['51, 102, 255'] = 0x31
excel_color_dict['51, 204, 204'] = 0x32
excel_color_dict['153, 204, 0'] = 0x33
excel_color_dict['255, 204, 0'] = 0x34
excel_color_dict['255, 153, 0'] = 0x35
excel_color_dict['255, 102, 0'] = 0x36
excel_color_dict['102, 102, 153'] = 0x37
excel_color_dict['150, 150, 150'] = 0x38
excel_color_dict['0, 51, 102'] = 0x39
excel_color_dict['51, 153, 102'] = 0x3A
excel_color_dict['0, 51, 0'] = 0x3B
excel_color_dict['51, 51, 0'] = 0x3C
excel_color_dict['153, 51, 0'] = 0x3D
excel_color_dict['51, 51, 153'] = 0x3E
excel_color_dict['51, 51, 51'] = 0x3F


@memoized
def rgb(c):
split = (c[0:2], c[2:4], c[4:6])
out = []
for x in split:
out.append(int(x,16))
return out

@memoized
def get_closest_rgb_match(hex):
hex = hex.replace("#",'').strip()
color_dict = excel_color_dict
orig_rgb = rgb(hex)
new_color = ''
min_distance = 195075
orig_r = orig_rgb[0]
orig_g = orig_rgb[1]
orig_b = orig_rgb[2]
for key in color_dict.keys():
new_r = int(key.split(',')[0])
new_g = int(key.split(',')[1])
new_b = int(key.split(',')[2])
r_distance = orig_r - new_r
g_distance = orig_g - new_g
b_distance = orig_b - new_b
current_distance = (r_distance * r_distance) + (g_distance * g_distance) + (b_distance * b_distance)
if current_distance < min_distance:
min_distance = current_distance
new_color = key
return color_dict.get(new_color)


48 changes: 48 additions & 0 deletions mm3/composer_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

import logging
from .model_base import HeaderFieldType

log = logging.getLogger(__name__)


class ComposerBase(object):
""" Used by Composers """
def run(self):
raise Exception("Overwrite run() in subclass")

def __init__(self, data_model, grid, document):
self.data_model = data_model
self.grid = grid
self.document = document
self.row_id = 0
self.col_id = 0

def row(self, row):
self.col_id = 0
self.start_new_row(self.row_id)
for cell in row:
self.write_cell(self.row_id, self.col_id, cell)
self.col_id += 1
self.end_row(self.row_id)
self.row_id += 1

def iterate_grid(self):
for row in self.grid.grid_data:
self.row(row)

def write_header(self):
i = 0
for header in self.data_model.field_titles:
cell = HeaderFieldType(data=header)
log.info(cell.__dict__)
self.write_cell(0, i, cell)
i += 1
self.row_id += 1

def set_option(self, x):
log.warn("%s not supported with this composer %s" % (x, self.__class__.__name__))

def finish(self):
""" Things we do after we are done """
for key in [x for x in dir(self.document.config) if not x.startswith("_")]:
self.set_option(key)
243 changes: 243 additions & 0 deletions mm3/composer_xls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import re
from .composer_base import ComposerBase
from . import lib.xlwt_0_7_2 as xlwt
from .lib.font_data.core import get_string_width
from .lib.xldate.convert import to_excel_from_C_codes
import logging
import io
from . import model_base
from . import style_base
from . import color_converter

log = logging.getLogger(__name__)


def get_string_width_from_style(char_string, style):
if type(char_string) not in (str, str):
return 0
point_size = style.font.height / 0x14 # convert back to points
font_name = style.font.name
if not font_name:
font_name = 'Arial'
return int(get_string_width(font_name, point_size, char_string) * 50)


class styleXLS(style_base.StyleBase):

font_points = 12

def get_pattern(self):
pattern = xlwt.Pattern()
# see issue #27 https://github.com/brianray/mm/issues/27
if not self.background_color:
return pattern
pattern.pattern = 1
color = color_converter.get_closest_rgb_match(self.background_color)
pattern.pattern_fore_colour = color
return pattern

def get_font_color(self):
color = 0
if self.color:
color = color_converter.get_closest_rgb_match(self.color)
return color

def get_border(self):
border = xlwt.Borders()
if False: # TODO borders
border.left = border.right = border.top = border.bottom = 3000
if self.border_color:
color = color_converter.get_closest_rgb_match(self.border_color)
border.top_color = color
border.bottom_color = color
border.left_color = color
border.right_color = color
return border

def is_bold(self):
if self.font_style == 'bold':
return True
return False

def get_font_points(self):
if self.font_size:
return self.font_size
return 12 # TODO: default from config?

def get_font_name(self):
if not self.font_family:
return 'Arial'
return self.font_family

def get_text_align(self):
text_align = xlwt.Alignment()
# HORZ - (0-General, 1-Left, 2-Center, 3-Right, 4-Filled, 5-Justified, 6-CenterAcrossSel, 7-Distributed)
horz = 0
if self.text_align == 'center':
horz = 2
elif self.text_align == 'right':
horz = 3
elif self.text_align == 'left':
horz = 1 # left
elif self.text_align is not None:
log.warn("Unknown text_align %s" % self.text_align)

text_align.horz = horz
return text_align


class ComposerXLS(ComposerBase):

def convert_style(self, stylestr):
in_style = styleXLS()
in_style.style_from_string(stylestr)

style = xlwt.XFStyle()
fnt1 = xlwt.Font()
fnt1.name = in_style.get_font_name()
fnt1.bold = in_style.is_bold()
fnt1.height = in_style.get_font_points() * 0x14
fnt1.colour_index = in_style.get_font_color()
style.font = fnt1
style.alignment = in_style.get_text_align()
style.pattern = in_style.get_pattern()
style.borders = in_style.get_border()

return style

def cell_to_value(self, cell, row_id):

if self.document.config.headers and row_id == 0:
css_like_style = self.document.config.header_style
elif len(self.document.config.row_styles) == 0:
css_like_style = ''
else:
style_index = row_id % len(self.document.config.row_styles)
css_like_style = self.document.config.row_styles[style_index]

style = self.convert_style(css_like_style)

if type(cell) == model_base.HeaderFieldType:
style = self.convert_style(self.document.config.header_style)
return cell.data, style

elif type(cell) in (model_base.IntFieldType, model_base.StringFieldType):
return cell.data, style

elif type(cell) == model_base.DateTimeFieldType:
style.num_format_str = self.document.config.get('datetime_format', 'M/D/YY h:mm')
return cell.data, style
elif type(cell) == model_base.DateFieldType:
num_string_format = self.document.config.get('date_format', 'M/D/YY')
if cell.format:
num_string_format = to_excel_from_C_codes(cell.format, self.document.config)
style.num_format_str = num_string_format
return cell.data, style

else:
return cell.data, style

def start_new_row(self, id):
pass

def end_row(self, id):
pass

def write_cell(self, row_id, col_id, cell):

value, style = self.cell_to_value(cell, row_id)
if type(cell) == model_base.ImageFieldType:
if cell.width:
self.sheet.col(col_id).width = cell.width * 256
if cell.height:
self.sheet.col(col_id).height = cell.height * 256
self.sheet.insert_bitmap(value, row_id, col_id)

elif type(cell) == model_base.URLFieldType:
self.sheet.write(
row_id,
col_id,
xlwt.Formula('HYPERLINK("%s";"%s")' % (value, cell.displayname)),
style
)
elif type(cell) == model_base.FormulaFieldType:
self.sheet.write(
row_id,
col_id,
xlwt.Formula(re.sub('^=', '', value)),
style
)

else:
# most cases
self.sheet.write(row_id, col_id, value, style)
self.done_write_cell(row_id, col_id, cell, value, style)

def done_write_cell(self, row_id, col_id, cell, value, style):

if self.document.config.get('adjust_all_col_width', False):

current_width = self.sheet.col_width(col_id) + 0x0d00
log.info("current width is %s" % current_width)
new_width = None

if type(cell) == model_base.StringFieldType:
new_width = get_string_width_from_style(value, style)

elif type(cell) == model_base.DateTimeFieldType:
new_width = 6550 # todo: different date formats

elif type(cell) == model_base.URLFieldType:
new_width = get_string_width_from_style(cell.displayname, style)

if new_width and new_width > current_width:
log.info("setting col #%s form width %s to %s" % (col_id, current_width, new_width))
col = self.sheet.col(col_id)
if new_width > 65535: # USHRT_MAX
new_width = 65534
current_width = new_width
col.width = new_width

def set_option(self, key):

val = getattr(self.document.config, key)
if key == 'freeze_col' and val and val > 0:
self.sheet.panes_frozen = True
self.sheet.vert_split_pos = val

elif key == 'freeze_row' and val and val > 0:
self.sheet.panes_frozen = True
self.sheet.horz_split_pos = val

else:

log.info("Nothing to be done for %s" % key)

return
log.info("Set option %s" % key)

def run(self, child=None):
top = False
if not child:
self.w = xlwt.Workbook(style_compression=2)
top = True
else:
self.w = child
self.sheet = self.w.add_sheet(self.document.name or "Sheet 1")
if self.document.config.headers:
self.write_header()
self.iterate_grid()
self.finish()

# process any childern
for doc_child in self.document.children:
doc_child.writestr(child=self.w)

if top:
# write the file to string
output = io.StringIO()
self.w.save(output)
contents = output.getvalue()
output.close()

return contents
Loading

0 comments on commit d56d58d

Please sign in to comment.