Skip to content

Commit d56d58d

Browse files
author
Brian Ray
committed
experimental python 3 support
1 parent 65744d4 commit d56d58d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+13789
-0
lines changed

mm3/__init__.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env python
2+
3+
__version__ = "0.1.3"
4+
__author__ = [
5+
"Brian Ray <[email protected]>",
6+
]
7+
__license__ = "TBD"
8+
9+
from .document_base import *
10+
from . import config_base
11+
from . import model_base
12+
import logging
13+
logging.basicConfig()
14+
15+
16+
Date = model_base.DateFieldType
17+
URL = model_base.URLFieldType
18+
Image = model_base.ImageFieldType
19+
Formula = model_base.FormulaFieldType
20+
21+
Config = config_base.ConfigBase

mm3/color_converter.py

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from .lib.font_data.decorators import memoized
2+
3+
excel_color_dict = {}
4+
5+
excel_color_dict['0, 0, 0'] = 0x08
6+
excel_color_dict['255, 255, 255'] = 0x09
7+
excel_color_dict['255, 0, 0'] = 0x0A
8+
excel_color_dict['0, 255, 0'] = 0x0B
9+
excel_color_dict['0, 0, 255'] = 0x0C
10+
excel_color_dict['255, 255, 0'] = 0x0D
11+
excel_color_dict['255, 0, 255'] = 0x0E
12+
excel_color_dict['0, 255, 255'] = 0x0F
13+
excel_color_dict['128, 0, 0'] = 0x10
14+
excel_color_dict['0, 128, 0'] = 0x11
15+
excel_color_dict['0, 0, 128'] = 0x12
16+
excel_color_dict['128, 128, 0'] = 0x13
17+
excel_color_dict['128, 0, 128'] = 0x14
18+
excel_color_dict['0, 128, 128'] = 0x15
19+
excel_color_dict['192, 192, 192'] = 0x16
20+
excel_color_dict['128, 128, 128'] = 0x17
21+
excel_color_dict['153, 153, 255'] = 0x18
22+
excel_color_dict['153, 51, 102'] = 0x1A
23+
excel_color_dict['255, 255, 204'] = 0x1C
24+
excel_color_dict['204, 255, 255'] = 0x1D
25+
excel_color_dict['102, 0, 102'] = 0x1E
26+
excel_color_dict['255, 128, 128'] = 0x1F
27+
excel_color_dict['0, 102, 204'] = 0x28
28+
excel_color_dict['204, 204, 255'] = 0x29
29+
excel_color_dict['0, 204, 255'] = 0x2A
30+
excel_color_dict['204, 255, 204'] = 0x2B
31+
excel_color_dict['255, 255, 153'] = 0x2C
32+
excel_color_dict['153, 204, 255'] = 0x2D
33+
excel_color_dict['255, 153, 204'] = 0x2E
34+
excel_color_dict['204, 153, 255'] = 0x2F
35+
excel_color_dict['255, 204, 153'] = 0x30
36+
excel_color_dict['51, 102, 255'] = 0x31
37+
excel_color_dict['51, 204, 204'] = 0x32
38+
excel_color_dict['153, 204, 0'] = 0x33
39+
excel_color_dict['255, 204, 0'] = 0x34
40+
excel_color_dict['255, 153, 0'] = 0x35
41+
excel_color_dict['255, 102, 0'] = 0x36
42+
excel_color_dict['102, 102, 153'] = 0x37
43+
excel_color_dict['150, 150, 150'] = 0x38
44+
excel_color_dict['0, 51, 102'] = 0x39
45+
excel_color_dict['51, 153, 102'] = 0x3A
46+
excel_color_dict['0, 51, 0'] = 0x3B
47+
excel_color_dict['51, 51, 0'] = 0x3C
48+
excel_color_dict['153, 51, 0'] = 0x3D
49+
excel_color_dict['51, 51, 153'] = 0x3E
50+
excel_color_dict['51, 51, 51'] = 0x3F
51+
52+
53+
@memoized
54+
def rgb(c):
55+
split = (c[0:2], c[2:4], c[4:6])
56+
out = []
57+
for x in split:
58+
out.append(int(x,16))
59+
return out
60+
61+
@memoized
62+
def get_closest_rgb_match(hex):
63+
hex = hex.replace("#",'').strip()
64+
color_dict = excel_color_dict
65+
orig_rgb = rgb(hex)
66+
new_color = ''
67+
min_distance = 195075
68+
orig_r = orig_rgb[0]
69+
orig_g = orig_rgb[1]
70+
orig_b = orig_rgb[2]
71+
for key in color_dict.keys():
72+
new_r = int(key.split(',')[0])
73+
new_g = int(key.split(',')[1])
74+
new_b = int(key.split(',')[2])
75+
r_distance = orig_r - new_r
76+
g_distance = orig_g - new_g
77+
b_distance = orig_b - new_b
78+
current_distance = (r_distance * r_distance) + (g_distance * g_distance) + (b_distance * b_distance)
79+
if current_distance < min_distance:
80+
min_distance = current_distance
81+
new_color = key
82+
return color_dict.get(new_color)
83+
84+

mm3/composer_base.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
import logging
3+
from .model_base import HeaderFieldType
4+
5+
log = logging.getLogger(__name__)
6+
7+
8+
class ComposerBase(object):
9+
""" Used by Composers """
10+
def run(self):
11+
raise Exception("Overwrite run() in subclass")
12+
13+
def __init__(self, data_model, grid, document):
14+
self.data_model = data_model
15+
self.grid = grid
16+
self.document = document
17+
self.row_id = 0
18+
self.col_id = 0
19+
20+
def row(self, row):
21+
self.col_id = 0
22+
self.start_new_row(self.row_id)
23+
for cell in row:
24+
self.write_cell(self.row_id, self.col_id, cell)
25+
self.col_id += 1
26+
self.end_row(self.row_id)
27+
self.row_id += 1
28+
29+
def iterate_grid(self):
30+
for row in self.grid.grid_data:
31+
self.row(row)
32+
33+
def write_header(self):
34+
i = 0
35+
for header in self.data_model.field_titles:
36+
cell = HeaderFieldType(data=header)
37+
log.info(cell.__dict__)
38+
self.write_cell(0, i, cell)
39+
i += 1
40+
self.row_id += 1
41+
42+
def set_option(self, x):
43+
log.warn("%s not supported with this composer %s" % (x, self.__class__.__name__))
44+
45+
def finish(self):
46+
""" Things we do after we are done """
47+
for key in [x for x in dir(self.document.config) if not x.startswith("_")]:
48+
self.set_option(key)

mm3/composer_xls.py

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import re
2+
from .composer_base import ComposerBase
3+
from . import lib.xlwt_0_7_2 as xlwt
4+
from .lib.font_data.core import get_string_width
5+
from .lib.xldate.convert import to_excel_from_C_codes
6+
import logging
7+
import io
8+
from . import model_base
9+
from . import style_base
10+
from . import color_converter
11+
12+
log = logging.getLogger(__name__)
13+
14+
15+
def get_string_width_from_style(char_string, style):
16+
if type(char_string) not in (str, str):
17+
return 0
18+
point_size = style.font.height / 0x14 # convert back to points
19+
font_name = style.font.name
20+
if not font_name:
21+
font_name = 'Arial'
22+
return int(get_string_width(font_name, point_size, char_string) * 50)
23+
24+
25+
class styleXLS(style_base.StyleBase):
26+
27+
font_points = 12
28+
29+
def get_pattern(self):
30+
pattern = xlwt.Pattern()
31+
# see issue #27 https://github.com/brianray/mm/issues/27
32+
if not self.background_color:
33+
return pattern
34+
pattern.pattern = 1
35+
color = color_converter.get_closest_rgb_match(self.background_color)
36+
pattern.pattern_fore_colour = color
37+
return pattern
38+
39+
def get_font_color(self):
40+
color = 0
41+
if self.color:
42+
color = color_converter.get_closest_rgb_match(self.color)
43+
return color
44+
45+
def get_border(self):
46+
border = xlwt.Borders()
47+
if False: # TODO borders
48+
border.left = border.right = border.top = border.bottom = 3000
49+
if self.border_color:
50+
color = color_converter.get_closest_rgb_match(self.border_color)
51+
border.top_color = color
52+
border.bottom_color = color
53+
border.left_color = color
54+
border.right_color = color
55+
return border
56+
57+
def is_bold(self):
58+
if self.font_style == 'bold':
59+
return True
60+
return False
61+
62+
def get_font_points(self):
63+
if self.font_size:
64+
return self.font_size
65+
return 12 # TODO: default from config?
66+
67+
def get_font_name(self):
68+
if not self.font_family:
69+
return 'Arial'
70+
return self.font_family
71+
72+
def get_text_align(self):
73+
text_align = xlwt.Alignment()
74+
# HORZ - (0-General, 1-Left, 2-Center, 3-Right, 4-Filled, 5-Justified, 6-CenterAcrossSel, 7-Distributed)
75+
horz = 0
76+
if self.text_align == 'center':
77+
horz = 2
78+
elif self.text_align == 'right':
79+
horz = 3
80+
elif self.text_align == 'left':
81+
horz = 1 # left
82+
elif self.text_align is not None:
83+
log.warn("Unknown text_align %s" % self.text_align)
84+
85+
text_align.horz = horz
86+
return text_align
87+
88+
89+
class ComposerXLS(ComposerBase):
90+
91+
def convert_style(self, stylestr):
92+
in_style = styleXLS()
93+
in_style.style_from_string(stylestr)
94+
95+
style = xlwt.XFStyle()
96+
fnt1 = xlwt.Font()
97+
fnt1.name = in_style.get_font_name()
98+
fnt1.bold = in_style.is_bold()
99+
fnt1.height = in_style.get_font_points() * 0x14
100+
fnt1.colour_index = in_style.get_font_color()
101+
style.font = fnt1
102+
style.alignment = in_style.get_text_align()
103+
style.pattern = in_style.get_pattern()
104+
style.borders = in_style.get_border()
105+
106+
return style
107+
108+
def cell_to_value(self, cell, row_id):
109+
110+
if self.document.config.headers and row_id == 0:
111+
css_like_style = self.document.config.header_style
112+
elif len(self.document.config.row_styles) == 0:
113+
css_like_style = ''
114+
else:
115+
style_index = row_id % len(self.document.config.row_styles)
116+
css_like_style = self.document.config.row_styles[style_index]
117+
118+
style = self.convert_style(css_like_style)
119+
120+
if type(cell) == model_base.HeaderFieldType:
121+
style = self.convert_style(self.document.config.header_style)
122+
return cell.data, style
123+
124+
elif type(cell) in (model_base.IntFieldType, model_base.StringFieldType):
125+
return cell.data, style
126+
127+
elif type(cell) == model_base.DateTimeFieldType:
128+
style.num_format_str = self.document.config.get('datetime_format', 'M/D/YY h:mm')
129+
return cell.data, style
130+
elif type(cell) == model_base.DateFieldType:
131+
num_string_format = self.document.config.get('date_format', 'M/D/YY')
132+
if cell.format:
133+
num_string_format = to_excel_from_C_codes(cell.format, self.document.config)
134+
style.num_format_str = num_string_format
135+
return cell.data, style
136+
137+
else:
138+
return cell.data, style
139+
140+
def start_new_row(self, id):
141+
pass
142+
143+
def end_row(self, id):
144+
pass
145+
146+
def write_cell(self, row_id, col_id, cell):
147+
148+
value, style = self.cell_to_value(cell, row_id)
149+
if type(cell) == model_base.ImageFieldType:
150+
if cell.width:
151+
self.sheet.col(col_id).width = cell.width * 256
152+
if cell.height:
153+
self.sheet.col(col_id).height = cell.height * 256
154+
self.sheet.insert_bitmap(value, row_id, col_id)
155+
156+
elif type(cell) == model_base.URLFieldType:
157+
self.sheet.write(
158+
row_id,
159+
col_id,
160+
xlwt.Formula('HYPERLINK("%s";"%s")' % (value, cell.displayname)),
161+
style
162+
)
163+
elif type(cell) == model_base.FormulaFieldType:
164+
self.sheet.write(
165+
row_id,
166+
col_id,
167+
xlwt.Formula(re.sub('^=', '', value)),
168+
style
169+
)
170+
171+
else:
172+
# most cases
173+
self.sheet.write(row_id, col_id, value, style)
174+
self.done_write_cell(row_id, col_id, cell, value, style)
175+
176+
def done_write_cell(self, row_id, col_id, cell, value, style):
177+
178+
if self.document.config.get('adjust_all_col_width', False):
179+
180+
current_width = self.sheet.col_width(col_id) + 0x0d00
181+
log.info("current width is %s" % current_width)
182+
new_width = None
183+
184+
if type(cell) == model_base.StringFieldType:
185+
new_width = get_string_width_from_style(value, style)
186+
187+
elif type(cell) == model_base.DateTimeFieldType:
188+
new_width = 6550 # todo: different date formats
189+
190+
elif type(cell) == model_base.URLFieldType:
191+
new_width = get_string_width_from_style(cell.displayname, style)
192+
193+
if new_width and new_width > current_width:
194+
log.info("setting col #%s form width %s to %s" % (col_id, current_width, new_width))
195+
col = self.sheet.col(col_id)
196+
if new_width > 65535: # USHRT_MAX
197+
new_width = 65534
198+
current_width = new_width
199+
col.width = new_width
200+
201+
def set_option(self, key):
202+
203+
val = getattr(self.document.config, key)
204+
if key == 'freeze_col' and val and val > 0:
205+
self.sheet.panes_frozen = True
206+
self.sheet.vert_split_pos = val
207+
208+
elif key == 'freeze_row' and val and val > 0:
209+
self.sheet.panes_frozen = True
210+
self.sheet.horz_split_pos = val
211+
212+
else:
213+
214+
log.info("Nothing to be done for %s" % key)
215+
216+
return
217+
log.info("Set option %s" % key)
218+
219+
def run(self, child=None):
220+
top = False
221+
if not child:
222+
self.w = xlwt.Workbook(style_compression=2)
223+
top = True
224+
else:
225+
self.w = child
226+
self.sheet = self.w.add_sheet(self.document.name or "Sheet 1")
227+
if self.document.config.headers:
228+
self.write_header()
229+
self.iterate_grid()
230+
self.finish()
231+
232+
# process any childern
233+
for doc_child in self.document.children:
234+
doc_child.writestr(child=self.w)
235+
236+
if top:
237+
# write the file to string
238+
output = io.StringIO()
239+
self.w.save(output)
240+
contents = output.getvalue()
241+
output.close()
242+
243+
return contents

0 commit comments

Comments
 (0)