def document_creation(self, dataframes, page_layout, record_id): try: doc_utils = DocumentUtilities() document = Document() section = document.sections[-1] section.orientation = WD_ORIENT.PORTRAIT section.page_width = Cm(doc_utils.get_cms(page_layout['page_width'])) section.page_height = Cm(doc_utils.get_cms(page_layout['page_height'])) section.left_margin = Cm(1.27) section.right_margin = Cm(1.27) section.top_margin = Cm(1.27) section.bottom_margin = Cm(1.27) document._body.clear_content() for index, df in enumerate(dataframes): for index, row in df.iterrows(): # if row['text'] == None and row['base64'] != None: # image_path = doc_utils.get_path_from_base64(self.DOWNLOAD_FOLDER, row['base64']) # document.add_picture(image_path, width=Cm(doc_utils.get_cms(row['text_width'])), # height=Cm(doc_utils.get_cms(row['text_height']))) # os.remove(image_path) if row['text'] != None and row['base64'] == None: paragraph = document.add_paragraph() paragraph_format = paragraph.paragraph_format paragraph_format.left_indent = Cm(doc_utils.get_cms(row['text_left'])) if index != df.index[-1] and df.iloc[index + 1]['text_top'] != row['text_top']: pixel = df.iloc[index + 1]['text_top'] - row['text_top'] - row['font_size'] if pixel>0: paragraph_format.space_after = Twips(doc_utils.pixel_to_twips(pixel)) else: paragraph_format.space_after = Twips(0) else: paragraph_format.space_after = Twips(0) run = paragraph.add_run() if row['font_family'] != None and "Bold" in row['font_family']: run.bold = True font = run.font font.name = 'Arial' if row['font_size'] != None: font.size = Twips(doc_utils.pixel_to_twips(row['font_size'])) run.add_text(row['text']) run.add_break(WD_BREAK.PAGE) out_filename = os.path.splitext(os.path.basename(record_id.split('|')[0]))[0] + str(uuid.uuid4()) + '_translated.docx' output_filepath = os.path.join(self.DOWNLOAD_FOLDER , out_filename) document.save(output_filepath) log_info("docx file formation done!! filename: %s"%out_filename, MODULE_CONTEXT) return out_filename except Exception as e: log_exception("dataframe to doc formation failed", MODULE_CONTEXT, e)
def image_for_docx(fileref, question, tpl, width=None): if fileref.__class__.__name__ in ('DAFile', 'DAFileList', 'DAFileCollection', 'DALocalFile', 'DAStaticFile'): file_info = dict(fullpath=fileref.path()) else: file_info = server.file_finder(fileref, convert={'svg': 'png'}, question=question) if 'fullpath' not in file_info: return '[FILE NOT FOUND]' if width is not None: m = re.search(r'^([0-9\.]+) *([A-Za-z]*)', str(width)) if m: amount = float(m.group(1)) units = m.group(2).lower() if units in ['in', 'inches', 'inch']: the_width = Inches(amount) elif units in ['pt', 'pts', 'point', 'points']: the_width = Pt(amount) elif units in ['mm', 'millimeter', 'millimeters']: the_width = Mm(amount) elif units in ['cm', 'centimeter', 'centimeters']: the_width = Cm(amount) elif units in ['twp', 'twip', 'twips']: the_width = Twips(amount) else: the_width = Pt(amount) else: the_width = Inches(2) else: the_width = Inches(2) return InlineImage(tpl, file_info['fullpath'], the_width)
def getMarginFromStyle(style,side): margin = style._element.xpath('.//w:tblCellMar/w:%s/@w:w'%side) marginType = style._element.xpath('.//w:tblCellMar/w:%s/@w:type'%side) if not margin and not marginType and style.base_style: return getMarginFromStyle(style.base_style,side) if marginType[0] != 'dxa': logger.error("Ok, I don't manage this case. Open an issue : Margin type support %s" % marginType) return Twips(int(margin[0]))
def transform_for_docx(text, question, tpl, width=None): if type(text) in (int, float, bool, NoneType): return text text = text_type(text) m = re.search(r'\[FILE ([^,\]]+), *([0-9\.]) *([A-Za-z]+) *\]', text) if m: amount = m.group(2) units = m.group(3).lower() if units in ['in', 'inches', 'inch']: the_width = Inches(amount) elif units in ['pt', 'pts', 'point', 'points']: the_width = Pt(amount) elif units in ['mm', 'millimeter', 'millimeters']: the_width = Mm(amount) elif units in ['cm', 'centimeter', 'centimeters']: the_width = Cm(amount) elif units in ['twp', 'twip', 'twips']: the_width = Twips(amount) else: the_width = Pt(amount) file_info = server.file_finder(m.group(1), convert={'svg': 'png'}, question=question) if 'fullpath' not in file_info: return '[FILE NOT FOUND]' return InlineImage(tpl, file_info['fullpath'], the_width) m = re.search(r'\[FILE ([^,\]]+)\]', text) if m: file_info = server.file_finder(m.group(1), convert={'svg': 'png'}, question=question) if 'fullpath' not in file_info: return '[FILE NOT FOUND]' return InlineImage(tpl, file_info['fullpath'], Inches(2)) return docassemble.base.filter.docx_template_filter(text, question=question)
class DescribeTabStop(object): def it_knows_its_position(self, position_get_fixture): tab_stop, expected_value = position_get_fixture assert tab_stop.position == expected_value def it_can_change_its_position(self, position_set_fixture): tab_stop, value, tabs, new_idx, expected_xml = position_set_fixture tab_stop.position = value assert tab_stop._tab is tabs[new_idx] assert tabs.xml == expected_xml def it_knows_its_alignment(self, alignment_get_fixture): tab_stop, expected_value = alignment_get_fixture assert tab_stop.alignment == expected_value def it_can_change_its_alignment(self, alignment_set_fixture): tab_stop, value, expected_xml = alignment_set_fixture tab_stop.alignment = value assert tab_stop._element.xml == expected_xml def it_knows_its_leader(self, leader_get_fixture): tab_stop, expected_value = leader_get_fixture assert tab_stop.leader == expected_value def it_can_change_its_leader(self, leader_set_fixture): tab_stop, value, expected_xml = leader_set_fixture tab_stop.leader = value assert tab_stop._element.xml == expected_xml # fixture -------------------------------------------------------- @pytest.fixture(params=[ ('w:tab{w:val=left}', 'LEFT'), ('w:tab{w:val=right}', 'RIGHT'), ]) def alignment_get_fixture(self, request): tab_stop_cxml, member = request.param tab_stop = TabStop(element(tab_stop_cxml)) expected_value = getattr(WD_TAB_ALIGNMENT, member) return tab_stop, expected_value @pytest.fixture(params=[ ('w:tab{w:val=left}', 'RIGHT', 'w:tab{w:val=right}'), ('w:tab{w:val=right}', 'LEFT', 'w:tab{w:val=left}'), ]) def alignment_set_fixture(self, request): tab_stop_cxml, member, expected_cxml = request.param tab_stop = TabStop(element(tab_stop_cxml)) expected_xml = xml(expected_cxml) value = getattr(WD_TAB_ALIGNMENT, member) return tab_stop, value, expected_xml @pytest.fixture(params=[ ('w:tab', 'SPACES'), ('w:tab{w:leader=none}', 'SPACES'), ('w:tab{w:leader=dot}', 'DOTS'), ]) def leader_get_fixture(self, request): tab_stop_cxml, member = request.param tab_stop = TabStop(element(tab_stop_cxml)) expected_value = getattr(WD_TAB_LEADER, member) return tab_stop, expected_value @pytest.fixture(params=[ ('w:tab', 'DOTS', 'w:tab{w:leader=dot}'), ('w:tab{w:leader=dot}', 'DASHES', 'w:tab{w:leader=hyphen}'), ('w:tab{w:leader=hyphen}', 'SPACES', 'w:tab'), ('w:tab{w:leader=hyphen}', None, 'w:tab'), ('w:tab', 'SPACES', 'w:tab'), ('w:tab', None, 'w:tab'), ]) def leader_set_fixture(self, request): tab_stop_cxml, new_value, expected_cxml = request.param tab_stop = TabStop(element(tab_stop_cxml)) value = ( None if new_value is None else getattr(WD_TAB_LEADER, new_value) ) expected_xml = xml(expected_cxml) return tab_stop, value, expected_xml @pytest.fixture def position_get_fixture(self, request): tab_stop = TabStop(element('w:tab{w:pos=720}')) return tab_stop, Twips(720) @pytest.fixture(params=[ ('w:tabs/w:tab{w:pos=360,w:val=left}', Twips(720), 0, 'w:tabs/w:tab{w:pos=720,w:val=left}'), ('w:tabs/(w:tab{w:pos=360,w:val=left},w:tab{w:pos=720,w:val=left})', Twips(180), 0, 'w:tabs/(w:tab{w:pos=180,w:val=left},w:tab{w:pos=720,w:val=left})'), ('w:tabs/(w:tab{w:pos=360,w:val=left},w:tab{w:pos=720,w:val=left})', Twips(960), 1, 'w:tabs/(w:tab{w:pos=720,w:val=left},w:tab{w:pos=960,w:val=left})'), ('w:tabs/(w:tab{w:pos=-72,w:val=left},w:tab{w:pos=-36,w:val=left})', Twips(-48), 0, 'w:tabs/(w:tab{w:pos=-48,w:val=left},w:tab{w:pos=-36,w:val=left})'), ('w:tabs/(w:tab{w:pos=-72,w:val=left},w:tab{w:pos=-36,w:val=left})', Twips(-16), 1, 'w:tabs/(w:tab{w:pos=-36,w:val=left},w:tab{w:pos=-16,w:val=left})'), ]) def position_set_fixture(self, request): tabs_cxml, value, new_idx, expected_cxml = request.param tabs = element(tabs_cxml) tab = tabs.tab_lst[0] tab_stop = TabStop(tab) expected_xml = xml(expected_cxml) return tab_stop, value, tabs, new_idx, expected_xml
class DescribeTabStops(object): def it_knows_its_length(self, len_fixture): tab_stops, expected_value = len_fixture assert len(tab_stops) == expected_value def it_can_iterate_over_its_tab_stops(self, iter_fixture): tab_stops, expected_count, tab_stop_, TabStop_, expected_calls = ( iter_fixture ) count = 0 for tab_stop in tab_stops: assert tab_stop is tab_stop_ count += 1 assert count == expected_count assert TabStop_.call_args_list == expected_calls def it_can_get_a_tab_stop_by_index(self, index_fixture): tab_stops, idx, TabStop_, tab, tab_stop_ = index_fixture tab_stop = tab_stops[idx] TabStop_.assert_called_once_with(tab) assert tab_stop is tab_stop_ def it_raises_on_indexed_access_when_empty(self): tab_stops = TabStops(element('w:pPr')) with pytest.raises(IndexError): tab_stops[0] def it_can_add_a_tab_stop(self, add_tab_fixture): tab_stops, position, kwargs, expected_xml = add_tab_fixture tab_stops.add_tab_stop(position, **kwargs) assert tab_stops._element.xml == expected_xml def it_can_delete_a_tab_stop(self, del_fixture): tab_stops, idx, expected_xml = del_fixture del tab_stops[idx] assert tab_stops._element.xml == expected_xml def it_raises_on_del_idx_invalid(self, del_raises_fixture): tab_stops, idx = del_raises_fixture with pytest.raises(IndexError) as exc: del tab_stops[idx] assert exc.value.args[0] == 'tab index out of range' def it_can_clear_all_its_tab_stops(self, clear_all_fixture): tab_stops, expected_xml = clear_all_fixture tab_stops.clear_all() assert tab_stops._element.xml == expected_xml # fixture -------------------------------------------------------- @pytest.fixture(params=[ 'w:pPr', 'w:pPr/w:tabs/w:tab{w:pos=42}', 'w:pPr/w:tabs/(w:tab{w:pos=24},w:tab{w:pos=42})', ]) def clear_all_fixture(self, request): pPr_cxml = request.param tab_stops = TabStops(element(pPr_cxml)) expected_xml = xml('w:pPr') return tab_stops, expected_xml @pytest.fixture(params=[ ('w:pPr/w:tabs/w:tab{w:pos=42}', 0, 'w:pPr'), ('w:pPr/w:tabs/(w:tab{w:pos=24},w:tab{w:pos=42})', 0, 'w:pPr/w:tabs/w:tab{w:pos=42}'), ('w:pPr/w:tabs/(w:tab{w:pos=24},w:tab{w:pos=42})', 1, 'w:pPr/w:tabs/w:tab{w:pos=24}'), ]) def del_fixture(self, request): pPr_cxml, idx, expected_cxml = request.param tab_stops = TabStops(element(pPr_cxml)) expected_xml = xml(expected_cxml) return tab_stops, idx, expected_xml @pytest.fixture(params=[ ('w:pPr', 0), ('w:pPr/w:tabs/w:tab{w:pos=42}', 1), ]) def del_raises_fixture(self, request): tab_stops_cxml, idx = request.param tab_stops = TabStops(element(tab_stops_cxml)) return tab_stops, idx @pytest.fixture(params=[ ('w:pPr', Twips(42), {}, 'w:pPr/w:tabs/w:tab{w:pos=42,w:val=left}'), ('w:pPr', Twips(72), {'alignment': WD_TAB_ALIGNMENT.RIGHT}, 'w:pPr/w:tabs/w:tab{w:pos=72,w:val=right}'), ('w:pPr', Twips(24), {'alignment': WD_TAB_ALIGNMENT.CENTER, 'leader': WD_TAB_LEADER.DOTS}, 'w:pPr/w:tabs/w:tab{w:pos=24,w:val=center,w:leader=dot}'), ('w:pPr/w:tabs/w:tab{w:pos=42}', Twips(72), {}, 'w:pPr/w:tabs/(w:tab{w:pos=42},w:tab{w:pos=72,w:val=left})'), ('w:pPr/w:tabs/w:tab{w:pos=42}', Twips(24), {}, 'w:pPr/w:tabs/(w:tab{w:pos=24,w:val=left},w:tab{w:pos=42})'), ('w:pPr/w:tabs/w:tab{w:pos=42}', Twips(42), {}, 'w:pPr/w:tabs/(w:tab{w:pos=42},w:tab{w:pos=42,w:val=left})'), ]) def add_tab_fixture(self, request): pPr_cxml, position, kwargs, expected_cxml = request.param tab_stops = TabStops(element(pPr_cxml)) expected_xml = xml(expected_cxml) return tab_stops, position, kwargs, expected_xml @pytest.fixture(params=[ ('w:pPr/w:tabs/w:tab{w:pos=0}', 0), ('w:pPr/w:tabs/(w:tab{w:pos=1},w:tab{w:pos=2},w:tab{w:pos=3})', 1), ('w:pPr/w:tabs/(w:tab{w:pos=4},w:tab{w:pos=5},w:tab{w:pos=6})', 2), ]) def index_fixture(self, request, TabStop_, tab_stop_): pPr_cxml, idx = request.param pPr = element(pPr_cxml) tab = pPr.xpath('./w:tabs/w:tab')[idx] tab_stops = TabStops(pPr) return tab_stops, idx, TabStop_, tab, tab_stop_ @pytest.fixture(params=[ ('w:pPr', 0), ('w:pPr/w:tabs/w:tab{w:pos=2880}', 1), ('w:pPr/w:tabs/(w:tab{w:pos=2880},w:tab{w:pos=5760})', 2), ]) def iter_fixture(self, request, TabStop_, tab_stop_): pPr_cxml, expected_count = request.param pPr = element(pPr_cxml) tab_elms = pPr.xpath('//w:tab') tab_stops = TabStops(pPr) expected_calls = [call(tab) for tab in tab_elms] return tab_stops, expected_count, tab_stop_, TabStop_, expected_calls @pytest.fixture(params=[ ('w:pPr', 0), ('w:pPr/w:tabs/w:tab{w:pos=2880}', 1), ]) def len_fixture(self, request): tab_stops_cxml, expected_value = request.param tab_stops = TabStops(element(tab_stops_cxml)) return tab_stops, expected_value # fixture components --------------------------------------------- @pytest.fixture def TabStop_(self, request, tab_stop_): return class_mock( request, 'docx.text.tabstops.TabStop', return_value=tab_stop_ ) @pytest.fixture def tab_stop_(self, request): return instance_mock(request, TabStop)
def position_get_fixture(self, request): tab_stop = TabStop(element('w:tab{w:pos=720}')) return tab_stop, Twips(720)
def convert_twips_to_cm(twips): width = Twips(int(twips)) return round(width.mm / 10, 2)
def export_to_word(vuln_info, template, output_file='openvas_report.docx'): """ Export vulnerabilities info in a Word file. :param vuln_info: Vulnerability list info :type vuln_info: list(Vulnerability) :param output_file: Filename of the Excel file :type output_file: str :param template: Path to Docx template :type template: str :raises: TypeError """ import matplotlib.pyplot as plt import numpy as np import tempfile import os import math from docx import Document from docx.oxml.shared import qn, OxmlElement from docx.oxml.ns import nsdecls from docx.oxml import parse_xml from docx.shared import Cm, Pt, Twips from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING from docx.enum.section import WD_ORIENT from docx.enum.style import WD_STYLE_TYPE from docx.enum.table import WD_ALIGN_VERTICAL from docx.shared import RGBColor if not isinstance(vuln_info, list): raise TypeError("Expected list, got '{}' instead".format( type(vuln_info))) else: for x in vuln_info: if not isinstance(x, Vulnerability): raise TypeError( "Expected Vulnerability, got '{}' instead".format(type(x))) if not isinstance(output_file, str): raise TypeError("Expected str, got '{}' instead".format( type(output_file))) else: if not output_file: raise ValueError("output_file must have a valid name.") if template is not None: if not isinstance(template, str): raise TypeError("Expected str, got '{}' instead".format( type(template))) vuln_info, vuln_levels, vuln_host_by_level, vuln_by_family = _get_collections( vuln_info) # ==================== # DOCUMENT PROPERTIES # ==================== # Create new doc if template is None: document = Document() doc_section = document.sections[0] # Set A4 Format doc_section.page_width = Cm(21.0) doc_section.page_height = Cm(29.7) # Shrink margins almost to 0 doc_section.left_margin = Cm(1.5) doc_section.right_margin = doc_section.left_margin doc_section.top_margin = Cm(1.0) doc_section.bottom_margin = Cm(1.0) # Force portrait doc_section.orientation = WD_ORIENT.PORTRAIT # use template else: document = Document(template) doc_section = document.sections[0] # Defining styles (if not defined already) # All used style will be custom, and with 'OR-' prefix. # In this way, the template can still define styles. doc_styles = document.styles # Base paragraph if 'OR-base' not in doc_styles: style_pr_base = doc_styles.add_style('OR-base', WD_STYLE_TYPE.PARAGRAPH) style_pr_base.font.name = 'Ubuntu' style_pr_base.font.size = Pt(8) style_pr_base.font.color.rgb = RGBColor.from_string('080808') style_pr_base.paragraph_format.left_indent = Cm(0) style_pr_base.paragraph_format.right_indent = Cm(0) style_pr_base.paragraph_format.first_line_indent = Cm(0) style_pr_base.paragraph_format.space_before = Cm(0) style_pr_base.paragraph_format.space_after = Cm(0) style_pr_base.paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE style_pr_base.paragraph_format.widow_control = True style_pr_base.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY_LOW # Base Styles modification if 'OR-base_bold' not in doc_styles: style_pr_body = doc_styles.add_style('OR-base_bold', WD_STYLE_TYPE.PARAGRAPH) style_pr_body.base_style = doc_styles['OR-base'] style_pr_body.font.bold = True # Section headers if 'OR-Heading_base' not in doc_styles: style_pr_or_head_base = doc_styles.add_style('OR-Heading_base', WD_STYLE_TYPE.PARAGRAPH) style_pr_or_head_base.base_style = doc_styles['OR-base_bold'] style_pr_or_head_base.font.color.rgb = RGBColor.from_string('183868') style_pr_or_head_base.paragraph_format.space_after = Pt(4) # - Titles if 'OR-Title' not in doc_styles: style_pr_or_title = doc_styles.add_style('OR-Title', WD_STYLE_TYPE.PARAGRAPH) style_pr_or_title.base_style = doc_styles['OR-Heading_base'] style_pr_or_title.font.size = Pt(36) style_pr_or_title.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER style_pr_or_head_base.paragraph_format.space_after = Pt(8) # - Headers if 'OR-Heading_1' not in doc_styles: style_pr_or_header = doc_styles.add_style('OR-Heading_1', WD_STYLE_TYPE.PARAGRAPH) style_pr_or_header.base_style = doc_styles['OR-Heading_base'] style_pr_or_header.font.size = Pt(20) if 'OR-Heading_2' not in doc_styles: style_pr_or_header = doc_styles.add_style('OR-Heading_2', WD_STYLE_TYPE.PARAGRAPH) style_pr_or_header.base_style = doc_styles['OR-Heading_base'] style_pr_or_header.font.size = Pt(16) if 'OR-Heading_3' not in doc_styles: style_pr_or_header = doc_styles.add_style('OR-Vuln_title', WD_STYLE_TYPE.PARAGRAPH) style_pr_or_header.base_style = doc_styles['OR-Heading_base'] style_pr_or_header.font.size = Pt(12) # - Vulnerabilities Titles for name, rgb in Config.colors().items(): if 'OR-Vuln_title_' + name not in doc_styles: style_pr_or_header = doc_styles.add_style('OR-Vuln_title_' + name, WD_STYLE_TYPE.PARAGRAPH) style_pr_or_header.base_style = doc_styles['OR-Vuln_title'] style_pr_or_header.font.color.rgb = RGBColor.from_string(rgb[1:]) # - Host with vulnerabilities title if 'OR-Vuln_hosts' not in doc_styles: style_pr_or_header = doc_styles.add_style('OR-Vuln_hosts', WD_STYLE_TYPE.PARAGRAPH) style_pr_or_header.base_style = doc_styles['OR-Heading_base'] style_pr_or_header.font.size = Pt(10) # TOC specific if 'OR-TOC_base' not in doc_styles: style_pr_or_toc_base = doc_styles.add_style('OR-TOC_base', WD_STYLE_TYPE.PARAGRAPH) style_pr_or_toc_base.base_style = doc_styles['OR-base'] style_pr_or_toc_base.font.color.rgb = RGBColor.from_string('183868') if 'OR-TOC_1' not in doc_styles: style_pr_or_toc = doc_styles.add_style('OR-TOC_1', WD_STYLE_TYPE.PARAGRAPH) style_pr_or_toc.base_style = doc_styles['OR-TOC_base'] style_pr_or_toc.font.bold = True if 'OR-TOC_2' not in doc_styles: style_pr_or_toc = doc_styles.add_style('OR-TOC_2', WD_STYLE_TYPE.PARAGRAPH) style_pr_or_toc.base_style = doc_styles['OR-TOC_base'] if 'OR-TOC_3' not in doc_styles: style_pr_or_toc = doc_styles.add_style('OR-Toc_3', WD_STYLE_TYPE.PARAGRAPH) style_pr_or_toc.base_style = doc_styles['OR-TOC_base'] if 'OR-TOC_4' not in doc_styles: style_pr_or_toc = doc_styles.add_style('OR-TOC_4', WD_STYLE_TYPE.PARAGRAPH) style_pr_or_toc.base_style = doc_styles['OR-TOC_base'] style_pr_or_toc.font.italic = True # Tables style # - Specific paragraph style to allow space before and after if 'OR-cell' not in doc_styles: style_pr_cell = doc_styles.add_style('OR-cell', WD_STYLE_TYPE.PARAGRAPH) style_pr_cell.base_style = doc_styles['OR-base'] style_pr_cell.paragraph_format.space_before = Pt(1.5) style_pr_cell.paragraph_format.space_after = Pt(1.5) if 'OR-cell_bold' not in doc_styles: style_pr_cell = doc_styles.add_style('OR-cell_bold', WD_STYLE_TYPE.PARAGRAPH) style_pr_cell.base_style = doc_styles['OR-cell'] style_pr_cell.font.bold = True # Clear all contents document._body.clear_content() # Set doc title doc_prop = document.core_properties doc_title = 'OpenVAS/GreenBone Report' doc_prop.title = doc_title doc_prop.category = "Report" # Effective writeable width # If margins set are float, try to fix (issue in python-docx: expected an int) # In this case, they _should be_ in twentieths of a point, so # multiply Twips helper try: doc_section.left_margin except ValueError as e: fixed_margin = float(re.search(": '(.+?)'", str(e)).group(1)) doc_section.left_margin = Twips(fixed_margin) try: doc_section.right_margin except ValueError as e: fixed_margin = float(re.search(": '(.+?)'", str(e)).group(1)) doc_section.right_margin = Twips(fixed_margin) page_width = doc_section.page_width - (doc_section.left_margin + doc_section.right_margin) ## Start actual document writing ## document.add_paragraph(doc_title, style='OR-Title') # ==================== # TABLE OF CONTENTS # ==================== # WARNING -Not working with LibreOffice document.add_paragraph('Table of Contents', style='OR-Heading_1') # keep the title as cover of the report document.add_page_break() par = document.add_paragraph(style='OR-base') run = par.add_run() fld_char = OxmlElement('w:fldChar') # creates a new element fld_char.set(qn('w:fldCharType'), 'begin') # sets attribute on element instr_text = OxmlElement('w:instrText') instr_text.set(qn('xml:space'), 'preserve') # sets attribute on element instr_text.text = r'TOC \h \z \t "OR-TOC_1;1;OR-OR-TOC_1;2;OR-TOC_3;3;OR-TOC_4;3"' fld_char2 = OxmlElement('w:fldChar') fld_char2.set(qn('w:fldCharType'), 'separate') fld_char3 = OxmlElement('w:t') fld_char3.text = "# Right-click to update field. #" fld_char2.append(fld_char3) fld_char4 = OxmlElement('w:fldChar') fld_char4.set(qn('w:fldCharType'), 'end') r_element = run._r r_element.append(fld_char) r_element.append(instr_text) r_element.append(fld_char2) r_element.append(fld_char4) document.add_page_break() # ==================== # MANAGEMENT SUMMARY # ==================== document.add_paragraph('Management Summary', style='OR-Heading_1') document.add_paragraph('< TYPE YOUR MANAGEMENT SUMMARY HERE >', style='OR-base') document.add_page_break() # ==================== # TECHNICAL FINDINGS # ==================== document.add_paragraph('Technical Findings', style='OR-Heading_1') document.add_paragraph( 'The section below discusses the technical findings.', style='OR-base') # -------------------- # SUMMARY TABLE # -------------------- document.add_paragraph('Summary', style='OR-Heading_2') colors_sum = [] labels_sum = [] vuln_sum = [] aff_sum = [] table_summary = document.add_table(rows=1, cols=3) # TABLE HEADERS # -------------------- hdr_cells = table_summary.rows[0].cells hdr_cells[0].paragraphs[0].add_run('Risk level') hdr_cells[1].paragraphs[0].add_run('Vulns number') hdr_cells[2].paragraphs[0].add_run('Affected hosts') # FIELDS # -------------------- # Provide data to table and charts for level in Config.levels().values(): row_cells = table_summary.add_row().cells row_cells[0].text = level.capitalize() row_cells[1].text = str(vuln_levels[level]) row_cells[2].text = str(vuln_host_by_level[level]) colors_sum.append(Config.colors()[level]) labels_sum.append(level) vuln_sum.append(vuln_levels[level]) aff_sum.append(vuln_host_by_level[level]) # Apply styles # -------------------- for h in hdr_cells: h.vertical_alignment = WD_ALIGN_VERTICAL.CENTER for p in h.paragraphs: p.style = doc_styles['OR-cell_bold'] for r in table_summary.rows[1:]: for c in r.cells: c.vertical_alignment = WD_ALIGN_VERTICAL.CENTER for p in c.paragraphs: p.style = doc_styles['OR-cell'] # -------------------- # CHART # -------------------- fd, path = tempfile.mkstemp(suffix='.png') chart_dpi = 144 chart_height = Cm(8) par_chart = document.add_paragraph(style='OR-base') par_chart.alignment = WD_ALIGN_PARAGRAPH.CENTER run_chart = par_chart.add_run() bar_chart, bar_axis = plt.subplots(dpi=chart_dpi) bar_axis.set_title('Vulnerability summary by risk level', fontsize=10) pos = np.arange(len(labels_sum)) width = 0.35 bar_axis.set_xticks(pos) bar_axis.set_xticklabels(labels_sum) bar_chart.gca().spines['left'].set_visible(False) bar_chart.gca().spines['right'].set_visible(False) bar_chart.gca().spines['top'].set_visible(False) bar_chart.gca().spines['bottom'].set_position('zero') bar_axis.tick_params(top=False, bottom=True, left=False, right=False, labelleft=False, labelbottom=True) bars_vuln = plt.bar(pos - width / 2, vuln_sum, width, align='center', label='Vulnerabilities', color=colors_sum, edgecolor='black') bars_aff = plt.bar(pos + width / 2, aff_sum, width, align='center', label='Affected hosts', color=colors_sum, edgecolor='black', hatch='//') for barcontainer in (bars_vuln, bars_aff): for bar in barcontainer: height = bar.get_height() bar_chart.gca().text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.3, str(int(height)), ha='center', color='black', fontsize=8) bar_chart.legend() bar_chart.savefig(path) # plt.show() # DEBUG bar_height = chart_height run_chart.add_picture(path, height=bar_height) os.remove(path) pie_chart, pie_axis = plt.subplots(dpi=chart_dpi, subplot_kw=dict(aspect="equal")) pie_axis.set_title('Vulnerability by family', fontsize=10) values = list(vuln_by_family.values()) pie, tx, autotexts = pie_axis.pie(values, labels=vuln_by_family.keys(), autopct='', textprops=dict(fontsize=8)) for i, txt in enumerate(autotexts): txt.set_text('{}'.format(values[i])) pie_chart.savefig(path) # plt.show() # DEBUG pie_height = chart_height run_chart.add_picture(path, height=pie_height) os.remove(path) # ==================== # VULN PAGES # ==================== cur_level = "" for i, vuln in enumerate(vuln_info, 1): # -------------------- # GENERAL # -------------------- level = vuln.level.lower() if level != cur_level: document.add_paragraph( level.capitalize(), style='OR-Heading_2').paragraph_format.page_break_before = True cur_level = level else: document.add_page_break() title = "[{}] {}".format(level.upper(), vuln.name) par = document.add_paragraph(title, style='OR-Vuln_title_' + vuln.level) table_vuln = document.add_table(rows=8, cols=3) table_vuln.autofit = False table_vuln.columns[0].width = Cm(0.35) table_vuln.columns[1].width = Cm(2.85) table_vuln.columns[-1].width = page_width for c in range(len(table_vuln.columns) - 1): table_vuln.columns[-1].width -= table_vuln.columns[c].width # COLOR # -------------------- col_cells = table_vuln.columns[0].cells col_cells[0].merge(col_cells[7]) color_fill = parse_xml(r'<w:shd {} w:fill="{}"/>'.format( nsdecls('w'), Config.colors()[vuln.level][1:])) col_cells[0]._tc.get_or_add_tcPr().append(color_fill) # TABLE HEADERS # -------------------- hdr_cells = table_vuln.columns[1].cells hdr_cells[0].paragraphs[0].add_run('Description') hdr_cells[1].paragraphs[0].add_run('Impact') hdr_cells[2].paragraphs[0].add_run('Recommendation') hdr_cells[3].paragraphs[0].add_run('Details') hdr_cells[4].paragraphs[0].add_run('CVSS') hdr_cells[5].paragraphs[0].add_run('CVEs') hdr_cells[6].paragraphs[0].add_run('Family') hdr_cells[7].paragraphs[0].add_run('References') # FIELDS # -------------------- cves = ", ".join(vuln.cves) cves = cves.upper() if cves != "" else "No CVE" cvss = str(vuln.cvss) if vuln.cvss != -1.0 else "No CVSS" txt_cells = table_vuln.columns[2].cells txt_cells[0].text = vuln.description txt_cells[1].text = vuln.impact txt_cells[2].text = vuln.solution txt_cells[3].text = vuln.insight txt_cells[4].text = cvss txt_cells[5].text = cves txt_cells[6].text = vuln.family txt_cells[7].text = vuln.references for c in txt_cells: for p in c.paragraphs: p.style = doc_styles['OR-cell'] # Apply styles # -------------------- for h in hdr_cells: for p in h.paragraphs: p.style = doc_styles['OR-cell_bold'] for c in txt_cells: for p in c.paragraphs: p.style = doc_styles['OR-cell'] # VULN HOSTS # -------------------- document.add_paragraph('Vulnerable hosts', style='OR-Vuln_hosts') # add coloumn for result per port and resize columns table_hosts = document.add_table(cols=5, rows=(len(vuln.hosts) + 1)) table_hosts.columns[0].width = Cm(2.8) table_hosts.columns[1].width = Cm(3.0) table_hosts.columns[2].width = Cm(2.0) table_hosts.columns[3].width = Cm(2.0) table_hosts.columns[-1].width = page_width for c in range(len(table_hosts.columns) - 1): table_hosts.columns[-1].width -= table_hosts.columns[c].width # TABLE HEADERS # -------------------- hdr_cells = table_hosts.rows[0].cells hdr_cells[0].paragraphs[0].add_run('IP') hdr_cells[1].paragraphs[0].add_run('Host name') hdr_cells[2].paragraphs[0].add_run('Port number') hdr_cells[3].paragraphs[0].add_run('Port protocol') hdr_cells[4].paragraphs[0].add_run('Port result') # FIELDS # -------------------- for j, (host, port) in enumerate(vuln.hosts, 1): cells = table_hosts.rows[j].cells cells[0].text = host.ip cells[1].text = host.host_name if host.host_name else "-" if port and port is not None: cells[2].text = "-" if port.number == 0 else str(port.number) cells[3].text = port.protocol cells[4].text = port.result else: cells[2].text = "No port info" # Apply styles # -------------------- for h in hdr_cells: for p in h.paragraphs: p.style = doc_styles['OR-cell_bold'] for r in table_hosts.rows[1:]: for c in r.cells: for p in c.paragraphs: p.style = doc_styles['OR-cell'] document.save(output_file)
def __init__(self, path: str, caption=None, width=Twips(5000), style=None): self.path = path self.caption = caption self.width = width self.style = style