############################################################################### # # Vml - A class for writing the Excel XLSX Vml file. # # Copyright 2013-2019, John McNamara, jmcnamara@cpan.org # # Package imports. from . import xmlwriter class Vml(xmlwriter.XMLwriter): """ A class for writing the Excel XLSX Vml file. """ ########################################################################### # # Public API. # ########################################################################### def __init__(self): """ Constructor. """ super(Vml, self).__init__() ########################################################################### # # Private API. # ########################################################################### def _assemble_xml_file(self, data_id, vml_shape_id, comments_data=None, buttons_data=None, header_images_data=None): # Assemble and write the XML file. z_index = 1 self._write_xml_namespace() # Write the o:shapelayout element. self._write_shapelayout(data_id) if buttons_data: # Write the v:shapetype element. self._write_button_shapetype() for button in buttons_data: # Write the v:shape element. vml_shape_id += 1 self._write_button_shape(vml_shape_id, z_index, button) z_index += 1 if comments_data: # Write the v:shapetype element. self._write_comment_shapetype() for comment in comments_data: # Write the v:shape element. vml_shape_id += 1 self._write_comment_shape(vml_shape_id, z_index, comment) z_index += 1 if header_images_data: # Write the v:shapetype element. self._write_image_shapetype() index = 1 for image in header_images_data: # Write the v:shape element. vml_shape_id += 1 self._write_image_shape(vml_shape_id, index, image) index += 1 self._xml_end_tag('xml') # Close the XML writer filehandle. self._xml_close() def _pixels_to_points(self, vertices): # Convert comment vertices from pixels to points. left, top, width, height = vertices[8:12] # Scale to pixels. left *= 0.75 top *= 0.75 width *= 0.75 height *= 0.75 return left, top, width, height ########################################################################### # # XML methods. # ########################################################################### def _write_xml_namespace(self): # Write the element. This is the root element of VML. schema = 'urn:schemas-microsoft-com:' xmlns = schema + 'vml' xmlns_o = schema + 'office:office' xmlns_x = schema + 'office:excel' attributes = [ ('xmlns:v', xmlns), ('xmlns:o', xmlns_o), ('xmlns:x', xmlns_x), ] self._xml_start_tag('xml', attributes) def _write_shapelayout(self, data_id): # Write the element. attributes = [('v:ext', 'edit')] self._xml_start_tag('o:shapelayout', attributes) # Write the o:idmap element. self._write_idmap(data_id) self._xml_end_tag('o:shapelayout') def _write_idmap(self, data_id): # Write the element. attributes = [ ('v:ext', 'edit'), ('data', data_id), ] self._xml_empty_tag('o:idmap', attributes) def _write_comment_shapetype(self): # Write the element. shape_id = '_x0000_t202' coordsize = '21600,21600' spt = 202 path = 'm,l,21600r21600,l21600,xe' attributes = [ ('id', shape_id), ('coordsize', coordsize), ('o:spt', spt), ('path', path), ] self._xml_start_tag('v:shapetype', attributes) # Write the v:stroke element. self._write_stroke() # Write the v:path element. self._write_comment_path('t', 'rect') self._xml_end_tag('v:shapetype') def _write_button_shapetype(self): # Write the element. shape_id = '_x0000_t201' coordsize = '21600,21600' spt = 201 path = 'm,l,21600r21600,l21600,xe' attributes = [ ('id', shape_id), ('coordsize', coordsize), ('o:spt', spt), ('path', path), ] self._xml_start_tag('v:shapetype', attributes) # Write the v:stroke element. self._write_stroke() # Write the v:path element. self._write_button_path() # Write the o:lock element. self._write_shapetype_lock() self._xml_end_tag('v:shapetype') def _write_image_shapetype(self): # Write the element. shape_id = '_x0000_t75' coordsize = '21600,21600' spt = 75 o_preferrelative = 't' path = 'm@4@5l@4@11@9@11@9@5xe' filled = 'f' stroked = 'f' attributes = [ ('id', shape_id), ('coordsize', coordsize), ('o:spt', spt), ('o:preferrelative', o_preferrelative), ('path', path), ('filled', filled), ('stroked', stroked), ] self._xml_start_tag('v:shapetype', attributes) # Write the v:stroke element. self._write_stroke() # Write the v:formulas element. self._write_formulas() # Write the v:path element. self._write_image_path() # Write the o:lock element. self._write_aspect_ratio_lock() self._xml_end_tag('v:shapetype') def _write_stroke(self): # Write the element. joinstyle = 'miter' attributes = [('joinstyle', joinstyle)] self._xml_empty_tag('v:stroke', attributes) def _write_comment_path(self, gradientshapeok, connecttype): # Write the element. attributes = [] if gradientshapeok: attributes.append(('gradientshapeok', 't')) attributes.append(('o:connecttype', connecttype)) self._xml_empty_tag('v:path', attributes) def _write_button_path(self): # Write the element. shadowok = 'f' extrusionok = 'f' strokeok = 'f' fillok = 'f' connecttype = 'rect' attributes = [ ('shadowok', shadowok), ('o:extrusionok', extrusionok), ('strokeok', strokeok), ('fillok', fillok), ('o:connecttype', connecttype), ] self._xml_empty_tag('v:path', attributes) def _write_image_path(self): # Write the element. extrusionok = 'f' gradientshapeok = 't' connecttype = 'rect' attributes = [ ('o:extrusionok', extrusionok), ('gradientshapeok', gradientshapeok), ('o:connecttype', connecttype), ] self._xml_empty_tag('v:path', attributes) def _write_shapetype_lock(self): # Write the element. ext = 'edit' shapetype = 't' attributes = [ ('v:ext', ext), ('shapetype', shapetype), ] self._xml_empty_tag('o:lock', attributes) def _write_rotation_lock(self): # Write the element. ext = 'edit' rotation = 't' attributes = [ ('v:ext', ext), ('rotation', rotation), ] self._xml_empty_tag('o:lock', attributes) def _write_aspect_ratio_lock(self): # Write the element. ext = 'edit' aspectratio = 't' attributes = [ ('v:ext', ext), ('aspectratio', aspectratio), ] self._xml_empty_tag('o:lock', attributes) def _write_comment_shape(self, shape_id, z_index, comment): # Write the element. shape_type = '#_x0000_t202' insetmode = 'auto' visibility = 'hidden' # Set the shape index. shape_id = '_x0000_s' + str(shape_id) # Get the comment parameters row = comment[0] col = comment[1] visible = comment[4] fillcolor = comment[5] vertices = comment[9] (left, top, width, height) = self._pixels_to_points(vertices) # Set the visibility. if visible: visibility = 'visible' style = ( 'position:absolute;' 'margin-left:%.15gpt;' 'margin-top:%.15gpt;' 'width:%.15gpt;' 'height:%.15gpt;' 'z-index:%d;' 'visibility:%s' % (left, top, width, height, z_index, visibility)) attributes = [ ('id', shape_id), ('type', shape_type), ('style', style), ('fillcolor', fillcolor), ('o:insetmode', insetmode), ] self._xml_start_tag('v:shape', attributes) # Write the v:fill element. self._write_comment_fill() # Write the v:shadow element. self._write_shadow() # Write the v:path element. self._write_comment_path(None, 'none') # Write the v:textbox element. self._write_comment_textbox() # Write the x:ClientData element. self._write_comment_client_data(row, col, visible, vertices) self._xml_end_tag('v:shape') def _write_button_shape(self, shape_id, z_index, button): # Write the element. shape_type = '#_x0000_t201' # Set the shape index. shape_id = '_x0000_s' + str(shape_id) # Get the button parameters. # row = button["_row"] # col = button["_col"] vertices = button["vertices"] (left, top, width, height) = self._pixels_to_points(vertices) style = ( 'position:absolute;' 'margin-left:%.15gpt;' 'margin-top:%.15gpt;' 'width:%.15gpt;' 'height:%.15gpt;' 'z-index:%d;' 'mso-wrap-style:tight' % (left, top, width, height, z_index)) attributes = [ ('id', shape_id), ('type', shape_type), ('style', style), ('o:button', 't'), ('fillcolor', 'buttonFace [67]'), ('strokecolor', 'windowText [64]'), ('o:insetmode', 'auto'), ] self._xml_start_tag('v:shape', attributes) # Write the v:fill element. self._write_button_fill() # Write the o:lock element. self._write_rotation_lock() # Write the v:textbox element. self._write_button_textbox(button["font"]) # Write the x:ClientData element. self._write_button_client_data(button) self._xml_end_tag('v:shape') def _write_image_shape(self, shape_id, z_index, image_data): # Write the element. shape_type = '#_x0000_t75' # Set the shape index. shape_id = '_x0000_s' + str(shape_id) # Get the image parameters width = image_data[0] height = image_data[1] name = image_data[2] position = image_data[3] x_dpi = image_data[4] y_dpi = image_data[5] # Scale the height/width by the resolution, relative to 72dpi. width = width * 72.0 / x_dpi height = height * 72.0 / y_dpi # Excel uses a rounding based around 72 and 96 dpi. width = 72.0 / 96 * int(width * 96.0 / 72 + 0.25) height = 72.0 / 96 * int(height * 96.0 / 72 + 0.25) style = ( 'position:absolute;' 'margin-left:0;' 'margin-top:0;' 'width:%.15gpt;' 'height:%.15gpt;' 'z-index:%d' % (width, height, z_index)) attributes = [ ('id', position), ('o:spid', shape_id), ('type', shape_type), ('style', style), ] self._xml_start_tag('v:shape', attributes) # Write the v:imagedata element. self._write_imagedata(z_index, name) # Write the o:lock element. self._write_rotation_lock() self._xml_end_tag('v:shape') def _write_comment_fill(self): # Write the element. color_2 = '#ffffe1' attributes = [('color2', color_2)] self._xml_empty_tag('v:fill', attributes) def _write_button_fill(self): # Write the element. color_2 = 'buttonFace [67]' detectmouseclick = 't' attributes = [ ('color2', color_2), ('o:detectmouseclick', detectmouseclick), ] self._xml_empty_tag('v:fill', attributes) def _write_shadow(self): # Write the element. on = 't' color = 'black' obscured = 't' attributes = [ ('on', on), ('color', color), ('obscured', obscured), ] self._xml_empty_tag('v:shadow', attributes) def _write_comment_textbox(self): # Write the element. style = 'mso-direction-alt:auto' attributes = [('style', style)] self._xml_start_tag('v:textbox', attributes) # Write the div element. self._write_div('left') self._xml_end_tag('v:textbox') def _write_button_textbox(self, font): # Write the element. style = 'mso-direction-alt:auto' attributes = [('style', style), ('o:singleclick', 'f')] self._xml_start_tag('v:textbox', attributes) # Write the div element. self._write_div('center', font) self._xml_end_tag('v:textbox') def _write_div(self, align, font=None): # Write the
element. style = 'text-align:' + align attributes = [('style', style)] self._xml_start_tag('div', attributes) if font: # Write the font element. self._write_font(font) self._xml_end_tag('div') def _write_font(self, font): # Write the element. caption = font["caption"] face = 'Calibri' size = 220 color = '#000000' attributes = [ ('face', face), ('size', size), ('color', color), ] self._xml_data_element('font', caption, attributes) def _write_comment_client_data(self, row, col, visible, vertices): # Write the element. object_type = 'Note' attributes = [('ObjectType', object_type)] self._xml_start_tag('x:ClientData', attributes) # Write the x:MoveWithCells element. self._write_move_with_cells() # Write the x:SizeWithCells element. self._write_size_with_cells() # Write the x:Anchor element. self._write_anchor(vertices) # Write the x:AutoFill element. self._write_auto_fill() # Write the x:Row element. self._write_row(row) # Write the x:Column element. self._write_column(col) # Write the x:Visible element. if visible: self._write_visible() self._xml_end_tag('x:ClientData') def _write_button_client_data(self, button): # Write the element. macro = button["macro"] vertices = button["vertices"] object_type = 'Button' attributes = [('ObjectType', object_type)] self._xml_start_tag('x:ClientData', attributes) # Write the x:Anchor element. self._write_anchor(vertices) # Write the x:PrintObject element. self._write_print_object() # Write the x:AutoFill element. self._write_auto_fill() # Write the x:FmlaMacro element. self._write_fmla_macro(macro) # Write the x:TextHAlign element. self._write_text_halign() # Write the x:TextVAlign element. self._write_text_valign() self._xml_end_tag('x:ClientData') def _write_move_with_cells(self): # Write the element. self._xml_empty_tag('x:MoveWithCells') def _write_size_with_cells(self): # Write the element. self._xml_empty_tag('x:SizeWithCells') def _write_visible(self): # Write the element. self._xml_empty_tag('x:Visible') def _write_anchor(self, vertices): # Write the element. (col_start, row_start, x1, y1, col_end, row_end, x2, y2) = vertices[:8] strings = [col_start, x1, row_start, y1, col_end, x2, row_end, y2] strings = [str(i) for i in strings] data = ", ".join(strings) self._xml_data_element('x:Anchor', data) def _write_auto_fill(self): # Write the element. data = 'False' self._xml_data_element('x:AutoFill', data) def _write_row(self, data): # Write the element. self._xml_data_element('x:Row', data) def _write_column(self, data): # Write the element. self._xml_data_element('x:Column', data) def _write_print_object(self): # Write the element. self._xml_data_element('x:PrintObject', 'False') def _write_text_halign(self): # Write the element. self._xml_data_element('x:TextHAlign', 'Center') def _write_text_valign(self): # Write the element. self._xml_data_element('x:TextVAlign', 'Center') def _write_fmla_macro(self, data): # Write the element. self._xml_data_element('x:FmlaMacro', data) def _write_imagedata(self, image_index, o_title): # Write the element. attributes = [ ('o:relid', 'rId' + str(image_index)), ('o:title', o_title), ] self._xml_empty_tag('v:imagedata', attributes) def _write_formulas(self): # Write the element. self._xml_start_tag('v:formulas') # Write the v:f elements. self._write_formula('if lineDrawn pixelLineWidth 0') self._write_formula('sum @0 1 0') self._write_formula('sum 0 0 @1') self._write_formula('prod @2 1 2') self._write_formula('prod @3 21600 pixelWidth') self._write_formula('prod @3 21600 pixelHeight') self._write_formula('sum @0 0 1') self._write_formula('prod @6 1 2') self._write_formula('prod @7 21600 pixelWidth') self._write_formula('sum @8 21600 0') self._write_formula('prod @7 21600 pixelHeight') self._write_formula('sum @10 21600 0') self._xml_end_tag('v:formulas') def _write_formula(self, eqn): # Write the element. attributes = [('eqn', eqn)] self._xml_empty_tag('v:f', attributes)