-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Brian Ray
committed
Aug 14, 2018
1 parent
65744d4
commit d56d58d
Showing
63 changed files
with
13,789 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.