def insert(self): if DEBUG: print("Adding list...") list_data = self.section.contents if isinstance(list_data, dict) and len(list_data) != 1: list_data = [list_data] if isinstance(list_data, dict) and len(list_data) == 1: # Create the parent title wrapper_table = self.cell_object.cell.add_table(rows=2, cols=1) title_cell = wrapper_table.cell(0, 0) title_text = list(list_data.keys())[0] insert_text(title_cell, title_text, self.style['title']) # Create a list in a list because this is a grouped list co = CellObject(wrapper_table.cell(1, 0)) table_data = list_data[title_text] invoke(co, Section('list', table_data, self.section.layout, {})) else: table.invoke(self.cell_object, Section('table', list_data, self.section.layout, {'list_style': True}))
def insert(self) -> None: """ This is a standing barchart (bar goes up) """ if DEBUG: print("Adding a column chart!") # Fix sizing size_w, size_h, dpi = utils.convert_plt_size(self.section) plt.figure(figsize=(size_w, size_h), dpi=dpi) data = self.section.contents objects = [i['name'] for i in data] y_axis = [i for i in range(len(objects))] x_axis = [i['data'][0] for i in data] colors = get_colors(self.section.layout, objects) rects = plt.bar(y_axis, x_axis, align='center', alpha=DEFAULT_BAR_ALPHA, width=DEFAULT_BAR_WIDTH, color=colors) ax = plt.gca() remove_plot_borders(ax) # Fix the legend values to be "some_value (some_number)" instead of # just "some_value" ledgend_keys = [ CHART_LABEL_NONE_STRING if i == '' else i for i in objects ] fixed_legends = [ f'{v} ({x_axis[i]})' for i, v in enumerate(ledgend_keys) ] # Move legend legend_location = 'upper center' legend_location_relative_to_graph = (0.5, -0.35) a = ax.legend(rects, fixed_legends, loc=legend_location, bbox_to_anchor=legend_location_relative_to_graph, handlelength=0.7) self.section = change_legend_vertical_alignment(self.section, top=3) set_legend_style(a, self.section.layout[LEGEND_STYLE]) ax.set_xlim(-len(objects), len(objects)) set_axis_font(ax) plt.xticks(y_axis, objects) plt.title(self.section.extra['title'], **self.style['title']) plt_b64 = utils.plt_t0_b64(plt) s = Section('image', plt_b64, {}, {'should_shrink': True}) image.invoke(self.cell_object, s)
def insert(self): if DEBUG: print('Adding md (external) image...') url = self.section.extra['src'] image_data = image_contents_from_url(url) img_section = Section('image', image_data, {}, {}) image.invoke(self.cell_object, img_section)
def insert(self): if DEBUG: print("Adding table...") table_data = self.section.contents if 'tableColumns' not in self.section.layout: return if 'readableHeaders' in self.section.layout: ordered = self.section.layout['tableColumns'] readable_headers = self.section.layout['readableHeaders'].values() table_columns = fix_order(ordered, readable_headers) else: table_columns = self.section.layout['tableColumns'] for i, header_text in enumerate(table_columns): if not isinstance(header_text, str): table_columns.remove(header_text) if 'title' in self.section.extra: table = self.cell_object.cell.add_table(rows=2, cols=len(table_columns)) title = table.cell(0, 0) title.merge(table.cell(0, len(table_columns) - 1)) insert_text(title, self.section.extra['title'], self.style['title']) hdr_cells = table.rows[1].cells else: table = self.cell_object.cell.add_table(rows=2, cols=len(table_columns)) hdr_cells = table.rows[0].cells table.style = DEFAULT_TABLE_STYLE if 'list_style' in self.section.extra and self.section.extra[ 'list_style']: table.style = None for i, header_text in enumerate(table_columns): insert_text(hdr_cells[i], header_text, self.style['text']) for r in table_data: row_cells = table.add_row().cells for i, header_text in enumerate(table_columns): if header_text not in r: continue # Old json format can have 'Avatars', which are images if isinstance(r[header_text], dict) and \ r[header_text]['type'] == 'image': row_temp = r[header_text] s = Section(row_temp['type'], row_temp['data'], {}, {}) co = CellObject(row_cells[i], add_run=False) image.invoke(co, s) else: insert_text(row_cells[i], r[header_text], self.style['text'])
def _insert_section(cell_object: CellObject, section: Section) -> None: section_type = section.type # Fix the chart name if section_type == 'chart': section_type = section.layout['chartType'] + '_chart' section.type = section_type insert_by_type(section_type, cell_object, section)
def invoke(cell_object: CellObject, section: Section, invoked_from_wrapper=False): if section.type != 'markdown': section.contents = f'Called markdown but not markdown - [{section}]' return error.invoke(cell_object, section) MarkdownWrapper(cell_object, section).wrap(invoked_from_wrapper=invoked_from_wrapper)
def add_header_logos(self): # Find the headers section = self.document.sections[0] section.header_distance = Pt(0) header = section.header table = header.add_table(rows=1, cols=2, width=Inches(24)) table.alignment = WD_TABLE_ALIGNMENT.CENTER table.autofit = True left_cell = table.cell(0, 0) right_cell = table.cell(0, 1) # Add the left cell to the header left_image = CellObject(left_cell) left_cell.paragraphs[-1].alignment = WD_PARAGRAPH_ALIGNMENT.LEFT left_cell.vertical_alignment = 1 # Add the right cell to the header right_image = CellObject(right_cell) right_cell.paragraphs[-1].alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT right_cell.vertical_alignment = 1 # Add the main logo left_logo_b64 = self.options.get('demistoLogo', XSOAR_LOGO_BASE64) s = Section('image', left_logo_b64, {}, {}) image.invoke(left_image, s) # Add the customer logo right_logo_b64 = self.options.get('customerLogo', False) if right_logo_b64: s = Section( 'image', right_logo_b64, {}, { 'max_size': { 'height': MAX_CUSTOMER_LOGO_HEIGHT_INCH, # max size in inches 'width': MAX_CUSTOMER_LOGO_WIDTH_INCH } }) image.invoke(right_image, s)
def insert_header(cell_object: Union[CellObject, _Cell], section: Union[str, Section], style={}, add_run=False, header='h1'): if isinstance(section, str): section = Section('header', section, {}, {}) section.set_style(style) section.extra = {**{'header_tag': header}, **section.extra} section.add_style(style) insert_elem(cell_object, section, add_run)
def insert_text(cell_object: Union[CellObject, _Cell], section: Union[str, Section], style={}, add_run=False): if isinstance(section, str): section = Section('text', section, {}, {}) section.set_style(style) section.type = 'text' section.add_style(style) insert_elem(cell_object, section, add_run)
def _insert_section(self, cell_object: CellObject, section: Section) -> None: section_type = section.type # Fix the chart name if section_type == 'chart': section_type = section.layout['chartType'] + '_chart' section.type = section_type insert_by_type(section_type, cell_object, section, trace=self.options.get('trace', False))
def _apply_cell_styling(cell_object: CellObject, section: Section): style = section.get_style() # Font size if PYDOCX_FONT_SIZE in style: cell_object.run.font.size = Pt(style[PYDOCX_FONT_SIZE]) # Set default font cell_object.run.font.name = DEFAULT_WORD_FONT # Font family if PYDOCX_FONT in style: cell_object.run.font.name = style[PYDOCX_FONT] # Other characteristics if PYDOCX_FONT_BOLD in style: cell_object.run.font.bold = style[PYDOCX_FONT_BOLD] if PYDOCX_FONT_STRIKE in style: cell_object.run.font.strike = style[PYDOCX_FONT_STRIKE] if PYDOCX_FONT_UNDERLINE in style: cell_object.run.font.underline = style[PYDOCX_FONT_UNDERLINE] if PYDOCX_FONT_ITALIC in style: cell_object.run.font.italic = style[PYDOCX_FONT_ITALIC] # Font color if PYDOCX_FONT_COLOR in style: if style[PYDOCX_FONT_COLOR][0] != '#': cell_object.run.font.color.rgb = name_to_rgb( style[PYDOCX_FONT_COLOR]) else: cell_object.run.font.color.rgb = hex_to_rgb( style[PYDOCX_FONT_COLOR]) # Background color if 'backgroundColor' in style: cell_object.cell = insert_cell_background( cell_object.cell, style[PYDOCX_BACKGROUND_COLOR]) # Paragraph styling if PYDOCX_TEXT_ALIGN in style: if style[PYDOCX_TEXT_ALIGN] == 'left': cell_object.paragraph.paragraph_format.alignment = ALIGN_LEFT elif style[PYDOCX_TEXT_ALIGN] == 'right': cell_object.paragraph.paragraph_format.alignment = ALIGN_RIGHT elif style[PYDOCX_TEXT_ALIGN] == 'center': cell_object.paragraph.paragraph_format.alignment = ALIGN_CENTER elif style[PYDOCX_TEXT_ALIGN] in [ ALIGN_RIGHT, ALIGN_CENTER, ALIGN_CENTER ]: cell_object.paragraph.paragraph_format.alignment = int( style[PYDOCX_TEXT_ALIGN])
def test_error(): """ To check the xpath: rename the .elements to .zip and open word/document.xml """ d = Document() t = d.add_table(1, 1) test_section = Section('tedxt', "some contents", {}, {}) c = CellObject(t.cell(0, 0)) # This will invoke an error element because of the wrong type: text.invoke(c, test_section) assert len(d.element.xpath('//w:p')) == 3 # Styles or error assert len(d.element.xpath('//w:i[@w:val="0"]')) == 1 assert len(d.element.xpath('//w:strike[@w:val="0"]')) == 1 assert len(d.element.xpath('//w:color[@w:val="FF0013"]')) == 1 assert len(d.element.xpath('//w:sz[@w:val="20"]')) == 1 assert len(d.element.xpath('//w:u[@w:val="none"]')) == 1
def insert(self): if DEBUG: print('Adding pie chart: ...') size_w, size_h, dpi = utils.convert_plt_size(self.section) fig, ax = plt.subplots(figsize=(size_w, size_h), dpi=dpi, subplot_kw=dict(aspect="equal")) data = [int(i['data'][0]) for i in self.section.contents] objects = [i['name'] for i in self.section.contents] # Fix the unassigned key: objects = [i if i != "" else "Unassigned" for i in objects] # Generate the default colors colors = get_colors(self.section.layout, objects) unassigned_color = 'darkgrey' # If we have predefined colors, use them if 'legend' in self.section.layout and self.section.layout['legend']: for i in self.section.layout['legend']: if 'color' in i: colors.append(i['color']) elif 'fill' in i: colors.append(i['fill']) color_keys = {} for i, k in enumerate(objects): color_keys[k] = colors[i] if k == 'Unassigned': color_keys['Unassigned'] = unassigned_color final_colors = [color_keys[k] for k in objects] wedges, texts = ax.pie(data, colors=final_colors, startangle=90, pctdistance=0.85, textprops=dict(color="w"), radius=1) keys_with_numbers = ['{}: {}'.format(k, data[i]) for i, k in enumerate(objects)] # legend_location_relative_to_graph = (1, 0, 0.5, 1) # legend_location = self.section.layout['legendStyle'] legend_location = 'upper center' legend_location_relative_to_graph = (0.5, 0) legend = ax.legend(wedges, keys_with_numbers, title="", loc=legend_location, bbox_to_anchor=legend_location_relative_to_graph, handlelength=0.7 ) set_legend_style(legend, self.section.layout[LEGEND_STYLE]) set_axis_font(ax) ax.set_title(self.section.extra['title'], **self.style['title']) circle = plt.Circle((0, 0), 0.7, fc='white') ax.add_artist(circle) plt_b64 = utils.plt_t0_b64(plt) s = Section('image', plt_b64, {}, {'should_shrink': True}) image.invoke(self.cell_object, s)
def insert_table_image(item, item_key, insertion_cell): row_temp = item[item_key] s = Section(row_temp['type'], row_temp['data'], {}, {}) co = CellObject(insertion_cell, add_run=False) image.invoke(co, s)
def insert(self): """ This is a bar chart on the side (bar goes right) """ if DEBUG: print("Adding a bar chart") # Fix sizing size_w, size_h, dpi = utils.convert_plt_size(self.section, self.cell_object) plt.figure(figsize=(size_w, size_h), dpi=dpi) data = self.section.contents x_axis = None if any([True for i in data if 'groups' in i and i['groups']]): # Note for future maintainer, I really really... hate stacked # bar charts, it made this file look like hell. I hope you cope # with matplotlib's shitt* implementation. # May the force be with you :pray: # Create the stacks agg = [] y_axis = [i['name'] for i in data] max_labels_stacked = [] for v in data: names = [i['name'] for i in v['groups']] max_labels_stacked = list(set(max_labels_stacked) | set(names)) labels = sorted(max_labels_stacked) colors = get_colors(self.section.layout, labels) for v in data: current_labels = {i['name']: i['data'][0] for i in v['groups']} cols = [] for l in labels: if l in current_labels: cols.append(current_labels[l]) else: cols.append(0) agg.append(cols) stacked = [i for i in zip(*agg)] # Draw each stack rects = [ plt.barh(y_axis, stacked[0], DEFAULT_BAR_WIDTH, color=colors.pop(0)) ] for i in range(1, len(stacked)): left_padding = [sum(i) for i in zip(*stacked[:i])] rects.append( plt.barh(y_axis, stacked[i], DEFAULT_BAR_WIDTH, left=left_padding, color=colors.pop(0))) ax = plt.gca() legend_location = 'upper center' legend_location_relative_to_graph = (0.5, -0.35) a = ax.legend(rects, labels, loc=legend_location, bbox_to_anchor=legend_location_relative_to_graph, handlelength=0.7) else: objects = [i['name'] for i in data] colors = get_colors(self.section.layout, objects) y_axis = [i for i in range(len(objects))] x_axis = [i['data'][0] for i in data] # Fix height of bar when only one present if len(objects) == 1: plt.margins(y=2) rects = plt.barh(y_axis, width=x_axis, align='center', alpha=DEFAULT_BAR_ALPHA, color=colors, height=DEFAULT_BAR_WIDTH) # Fix the legend values to be "some_value (some_number)" instead of # just "some_value" ledgend_keys = [ CHART_LABEL_NONE_STRING if i == '' else i for i in objects ] fixed_legends = [ f'{v} ({x_axis[i]})' for i, v in enumerate(ledgend_keys) ] # Create and move the legend outside ax = plt.gca() legend_location = 'upper center' legend_location_relative_to_graph = (0.5, -0.35) a = ax.legend(rects, fixed_legends, loc=legend_location, bbox_to_anchor=legend_location_relative_to_graph, handlelength=0.7) ax.set_yticklabels([]) # Style the axis and labels self.section = change_legend_vertical_alignment(self.section, top=1) set_legend_style(a, self.section.layout[LEGEND_STYLE]) # Fix the axises set_axis_font(ax) ax.set_yticks(y_axis) ax.set_xlabel('') ax.invert_yaxis() # labels read top-to-bottom # Fix the xaxis ratio to fit biggest element if x_axis: ax.set_xlim(0, max(x_axis) + X_AXIS_PADDING) # Remove the bottom labels remove_plot_borders(ax) plt.tick_params(bottom='off') plt.title(self.section.extra['title'], **self.style['title']) plt_b64 = utils.plt_t0_b64(plt, (size_w, size_h), dpi) s = Section('image', plt_b64, {}, {'should_shrink': True}) image.invoke(self.cell_object, s)
def wrap(self, invoked_from_wrapper=False): # Handle called from another wrapper. md_section_list = None if isinstance(self.section.contents, list): md_section_list = self.section.contents elif invoked_from_wrapper and \ isinstance(self.section.contents.contents, str): md_section_list = [self.section.contents] if not isinstance(md_section_list, list): raise ValueError('Markdown section does not have valid contents ' + '(must be a list)') for section in md_section_list: # === Start wrappers === if section.type == MD_TYPE_DIV: temp_section = MarkdownSection('markdown', section.contents, {}, {}) invoke(self.cell_object, temp_section) continue if section.type == MD_TYPE_CODE: md_code.invoke(self.cell_object, section) self.cell_object.update_paragraph() continue if section.type == MD_TYPE_QUOTE: md_blockquote.invoke(self.cell_object, section) self.cell_object.update_paragraph() continue if section.type == MD_TYPE_UNORDERED_LIST: md_ul.invoke(self.cell_object, section) self.cell_object.update_paragraph() continue if section.type == MD_TYPE_ORDERED_LIST: md_ol.invoke(self.cell_object, section) self.cell_object.update_paragraph() continue if section.type == MD_TYPE_LIST_ITEM: md_li.invoke(self.cell_object, section) continue if section.type == MD_TYPE_TABLE: table_html = section.extra['original_html'] t = PyQuery(table_html) headers = [i.find('th') for i in t.find('tr').items()][0] headers = [c.text() for c in headers.items()] rows = [ i.find('td') for i in t.find('tr').items() if i.find('td') ] data = [] for row in rows: r = { headers[i]: c.text() for i, c in enumerate(row.items()) } data.append(r) s = Section("table", data, {"tableColumns": headers}, {}) table.invoke(self.cell_object, s) continue # Fix wrapped: # (Some times there are elements which contain other elements, # but are not considered one of the declared wrappers) if isinstance(section.contents, list): is_inside_wrapper = False if 'inline' in section.extra: is_inside_wrapper = True if section.type == 'span': section.propagate_extra('check_newline', True, only_multiple_children=False) # TODO: Fix problem with H1 no newline even if in span. temp_section = MarkdownSection('markdown', section.contents, {}, section.extra, section.attrs) invoke(self.cell_object, temp_section, invoked_from_wrapper=is_inside_wrapper) continue # === Elements === if section.type in SHOULD_NEW_LINE and section.get_extra( 'check_newline'): self.cell_object.add_paragraph() if section.type == MD_TYPE_HORIZONTAL_LINE: md_hr.invoke(self.cell_object, section) continue # Add a block (newline) if not called from a wrapper # (Should come after hr) if not invoked_from_wrapper: self.cell_object.add_paragraph() if section.type in MD_TYPES_HEADERS: # We want to keep the h{1...6} for styling insert_header(self.cell_object, section.contents, header=section.type) continue if section.type in [MD_TYPE_TEXT, MD_TYPE_INLINE_TEXT]: if invoked_from_wrapper: self.cell_object.add_run() insert_text(self.cell_object, section) continue if section.type == MD_TYPE_LINK: md_link.invoke(self.cell_object, section) continue if section.type == MD_TYPE_IMAGE: md_image.invoke(self.cell_object, section) continue raise ValueError(f'Section type is not defined: {section.type}')
def insert(self): if DEBUG: print("Adding line chart...") # Fix sizing size_w, size_h, dpi = utils.convert_plt_size(self.section, self.cell_object) figure(num=2, figsize=(size_w, size_h), dpi=dpi, constrained_layout=False) data = self.section.contents fix_data(data) # Make the groups look like: # groups = { # 'Type A': { # dates: ['2000', '2001', '2002'] # values: ['1', '2', '3'] # } # 'Type B': { # dates: ['2000', '2001', '2002'], # values : ['4', '5', '6'] # } groups = fix_data(data) # Fix empty key if '' in groups.keys(): groups['None'] = groups.pop('') # Generate the default colors colors = get_colors(self.section.layout, groups.keys()) unassigned_color = 'darkgrey' # If we have predefined colors, use them if 'legend' in self.section.layout and self.section.layout['legend']: for i in self.section.layout['legend']: if 'color' in i: colors.append(i['color']) elif 'fill' in i: colors.append(i['fill']) color_keys = {} for i, k in enumerate(groups.keys()): color_keys[k] = colors[i] if k == 'Unassigned': color_keys['Unassigned'] = unassigned_color final_colors = {k: color_keys[k] for k in groups.keys()} # Plot the lines for group, line in groups.items(): x_axis = line['dates'] y_axis = line['values'] plt.plot(x_axis, y_axis, marker='', color=final_colors[group], linewidth=2) # Create and move the legend outside ax = plt.gca() # Auto rotate the labels remove_plot_borders(ax) legend_location = 'upper center' legend_location_relative_to_graph = (0.5, -0.35) handles = [plt.Rectangle((0, 0), 1, 1, fc=final_colors[i]) for i in groups.keys()] legend = ax.legend(handles, [i for i in groups.keys()], loc=legend_location, bbox_to_anchor=legend_location_relative_to_graph, handlelength=0.7, handleheight=0.7, ncol=2) self.section = change_legend_vertical_alignment(self.section, top=1) # Set max ticks in xaxis to be MAX_AXIS_LABELS set_legend_max_count(ax, self.cell_object) set_legend_style(legend, self.section.layout[LEGEND_STYLE]) set_axis_font(ax) ax.set_title(self.section.extra['title'], **self.style['title']) # Add to docx as image plt_b64 = utils.plt_t0_b64(plt, (size_w, size_h), dpi) s = Section('image', plt_b64, {}, {'should_shrink': True}) image.invoke(self.cell_object, s)
def insert(self): if DEBUG: print("Adding table...") table_data = self.section.contents if isinstance(table_data, dict): table_data = table_data.get('data', table_data) if 'tableColumns' not in self.section.layout: # Quick dirty fix for test - will be refactored in upcoming PR self.section.layout['tableColumns'] = list(table_data[0].keys()) # Fix new lists if isinstance(table_data, dict): wrapper_table = self.cell_object.cell.add_table( rows=2, cols=len(table_data.keys())) i = 0 # Add the wrapping headers for wrapper_header, table_contents in table_data.items(): hdr = wrapper_table.cell(0, i) insert_text(hdr, wrapper_header, self.style['title']) body = wrapper_table.cell(1, i) c = CellObject(body) # Hacky but will do the job invoke(c, Section('table', table_contents, {}, {})) i += 1 return if 'readableHeaders' in self.section.layout: ordered = self.section.layout['tableColumns'] readable_headers = self.section.layout['readableHeaders'] table_columns = fix_order(ordered, readable_headers) else: table_columns = self.section.layout['tableColumns'] # Quick fix, word crashes on more than 64 columns. # See: https://stackoverflow.com/questions/36921010/docx-does-not-support-more-than-63-columns-in-a-table table_columns = table_columns[0:63] for i, header_text in enumerate(table_columns): if not isinstance(header_text, str): table_columns.remove(header_text) if 'title' in self.section.extra: table = self.cell_object.cell.add_table(rows=2, cols=len(table_columns)) title = table.cell(0, 0) title.merge(table.cell(0, len(table_columns) - 1)) insert_text(title, self.section.extra['title'], self.style['title']) hdr_cells = table.rows[1].cells else: table = self.cell_object.cell.add_table(rows=2, cols=len(table_columns)) hdr_cells = table.rows[0].cells table.style = DEFAULT_TABLE_STYLE if 'list_style' in self.section.extra and self.section.extra[ 'list_style']: table.style = None for i, header_text in enumerate(table_columns): insert_text(hdr_cells[i], header_text, self.style['text']) if len(table_columns) > 63: # TODO: add error. pass for r in table_data: row_cells = table.add_row().cells for i, header_text in enumerate(table_columns): if header_text not in r: continue # Old json format can have 'Avatars', which are images if isinstance(r[header_text], dict) and \ r[header_text]['type'] == 'image': row_temp = r[header_text] s = Section(row_temp['type'], row_temp['data'], {}, {}) co = CellObject(row_cells[i], add_run=False) image.invoke(co, s) else: insert_text(row_cells[i], r[header_text], self.style['text'])
def _attach_all_styles(section: Section, base_style: dict) -> Section: attribute_styles = {k: True for k in section.attrs} section.add_style(base_style, is_new=False) section.add_style(attribute_styles) return section