def insert_horizontal_border(paragraph: Paragraph): """ Add an horizontal border under a paragraph. Args: paragraph: Paragraph under which you want to add an horizontal border. """ # access to XML paragraph element <w:p> and its properties p = paragraph._p pPr = p.get_or_add_pPr() # create new XML element and insert it to the paragraph element pBdr = OxmlElement('w:pBdr') pPr.insert_element_before( pBdr, 'w:shd', 'w:tabs', 'w:suppressAutoHyphens', 'w:kinsoku', 'w:wordWrap', 'w:overflowPunct', 'w:topLinePunct', 'w:autoSpaceDE', 'w:autoSpaceDN', 'w:bidi', 'w:adjustRightInd', 'w:snapToGrid', 'w:spacing', 'w:ind', 'w:contextualSpacing', 'w:mirrorIndents', 'w:suppressOverlap', 'w:jc', 'w:textDirection', 'w:textAlignment', 'w:textboxTightWrap', 'w:outlineLvl', 'w:divId', 'w:cnfStyle', 'w:rPr', 'w:sectPr', 'w:pPrChange') # create new XML element, set its properties and add it to the pBdr element bottom = OxmlElement('w:bottom') bottom.set(qn('w:val'), 'single') bottom.set(qn('w:sz'), '6') bottom.set(qn('w:space'), '1') bottom.set(qn('w:color'), 'auto') pBdr.append(bottom)
def insert_toc(self): paragraph = self.document.add_paragraph() run = paragraph.add_run() fldChar = OxmlElement('w:fldChar') # creates a new element fldChar.set(qn('w:fldCharType'), 'begin') # sets attribute on element instrText = OxmlElement('w:instrText') instrText.set(qn('xml:space'), 'preserve') # sets attribute on element instrText.text = 'TOC \\o "1-3" \\h \\z \\u' # change 1-3 depending on heading levels you need fldChar2 = OxmlElement('w:fldChar') fldChar2.set(qn('w:fldCharType'), 'separate') # fldChar3 = OxmlElement('w:t') fldChar3 = OxmlElement('w:updateFields') fldChar3.set(qn('w:val'), 'true') # fldChar3.text = "Right-click to update field." fldChar2.append(fldChar3) fldChar4 = OxmlElement('w:fldChar') fldChar4.set(qn('w:fldCharType'), 'end') r_element = run._r r_element.append(fldChar) r_element.append(instrText) r_element.append(fldChar2) r_element.append(fldChar4) p_element = paragraph._p
def add_table_of_contents(document, label='Table of Contents'): document.add_heading(label, level=1) paragraph = document.add_paragraph() run = paragraph.add_run() fldChar = OxmlElement('w:fldChar') # creates a new element fldChar.set(qn('w:fldCharType'), 'begin') # sets attribute on element instrText = OxmlElement('w:instrText') instrText.set(qn('xml:space'), 'preserve') # sets attribute on element instrText.text = r'TOC \o "1-3" \h \z \u' # change 1-3 depending on heading levels you need fldChar2 = OxmlElement('w:fldChar') fldChar2.set(qn('w:fldCharType'), 'separate') fldChar3 = OxmlElement('w:t') fldChar3.text = "Right-click to update field." fldChar2.append(fldChar3) fldChar4 = OxmlElement('w:fldChar') fldChar4.set(qn('w:fldCharType'), 'end') r_element = run._r r_element.append(fldChar) r_element.append(instrText) r_element.append(fldChar2) r_element.append(fldChar4) p_element = paragraph._p return document
def new(cls, pic_id, image_filename): nvPicPr = OxmlElement('pic:nvPicPr') nvPicPr.append(CT_NonVisualDrawingProps.new( 'pic:cNvPr', pic_id, image_filename )) nvPicPr.append(CT_NonVisualPictureProperties.new()) return nvPicPr
def set_table_margins(self, tbl, width: dict = None): """ <w:tbl> <w:tblPr> <w:tblStyle w:val="LightShading"/> <w:tblW w:type="auto" w:w="0"/> <w:tblCellMar> <w:left w:type="dxa" w:w="63"/> <w:right w:type="dxa" w:w="63"/> </w:tblCellMar> <w:tblLook w:firstColumn="1" w:firstRow="1" w:lastColumn="0" w:lastRow="0" w:noHBand="0" w:noVBand="1" w:val="04A0"/> </w:tblPr> """ # noqa # 67 = Cm(0.11) ...? if width is None: width = dict(left=67, right=67) margins = OxmlElement('w:tblCellMar') for side, w in width.items(): margin = OxmlElement(f'w:{side}') margin.set(qn('w:w'), str(w)) margin.set(qn('w:type'), 'dxa') margins.append(margin) tbl._tbl.tblPr.append(margins)
def generate_pw_toc(document): global gl_font_size add_simple_par(document, "", gl_font_size, align=docx.enum.text.WD_ALIGN_PARAGRAPH.CENTER, bold=False, breakpar=True) document.add_heading("Spis treści", 1) paragraph = document.add_paragraph() run = paragraph.add_run() fldChar = OxmlElement('w:fldChar') # creates a new element fldChar.set(qn('w:fldCharType'), 'begin') # sets attribute on element instrText = OxmlElement('w:instrText') instrText.set(qn('xml:space'), 'preserve') # sets attribute on element instrText.text = 'TOC \\o "1-2" \\h \\z \\u' # change 1-3 depending on heading levels you need fldChar2 = OxmlElement('w:fldChar') fldChar2.set(qn('w:fldCharType'), 'separate') fldChar3 = OxmlElement('w:t') fldChar3.text = "Kliknij prawym klawiszem myszki, aby zaktualizować spis treści." fldChar2.append(fldChar3) fldChar4 = OxmlElement('w:fldChar') fldChar4.set(qn('w:fldCharType'), 'end') r_element = run._r r_element.append(fldChar) r_element.append(instrText) r_element.append(fldChar2) r_element.append(fldChar4) p_element = paragraph._p
def add_url_hyperlink(paragraph, url: str, text: str): """Add URL hyperlink in docx report. Adapted from https://stackoverflow.com/a/47666747/906385 Args: paragraph: a docx paragraph object url (str): The URL text (str): The text to display """ # This gets access to the document.xml.rels file and gets a new relation id value part = paragraph.part r_id = part.relate_to(url, constants.RELATIONSHIP_TYPE.HYPERLINK, is_external=True) # Create the w:hyperlink tag and add needed values hyperlink = OxmlElement("w:hyperlink") hyperlink.set(qn("r:id"), r_id) # Create a w:r element and a new w:rPr element new_run = OxmlElement("w:r") rPr = OxmlElement("w:rPr") # Join all the xml elements together add add the required text to the w:r element new_run.append(rPr) new_run.text = text hyperlink.append(new_run) # Create a new Run object and add the hyperlink into it run = paragraph.add_run() run._r.append(hyperlink) # A workaround for the lack of a hyperlink style (doesn't go purple after using the link) # Delete this if using a template that has the hyperlink style in it run.font.color.theme_color = dml.MSO_THEME_COLOR_INDEX.HYPERLINK run.font.underline = True
def add_drawing(self, inline_or_anchor): """ Return a newly appended ``CT_Drawing`` (``<w:drawing>``) child element having *inline_or_anchor* as its child. """ drawing = OxmlElement('w:drawing') self.append(drawing) drawing.append(inline_or_anchor) return drawing
def new(cls): """ Return a new ``<w:tc>`` element, containing an empty paragraph as the required EG_BlockLevelElt. """ tc = OxmlElement("w:tc") p = CT_P.new() tc.append(p) return tc
def add_tbl_border(self, tbl): """Add table bottom border with OxmlElement""" borders = OxmlElement('w:tblBorders') bottom_border = OxmlElement('w:bottom') bottom_border.set(qn('w:val'), 'single') bottom_border.set(qn('w:sz'), '4') borders.append(bottom_border) tbl._tbl.tblPr.append(borders)
def new(cls): """ Return a new ``<w:tc>`` element, containing an empty paragraph as the required EG_BlockLevelElt. """ tc = OxmlElement('w:tc') p = CT_P.new() tc.append(p) return tc
def new(cls, num_id, abstractNum_id): """ Return a new ``<w:num>`` element having numId of *num_id* and having a ``<w:abstractNumId>`` child with val attribute set to *abstractNum_id*. """ abstractNumId = CT_DecimalNumber.new('w:abstractNumId', abstractNum_id) num = OxmlElement('w:num', {qn('w:numId'): str(num_id)}) num.append(abstractNumId) return num
def new(cls): """ Return a new ``<w:tbl>`` element, containing the required ``<w:tblPr>`` and ``<w:tblGrid>`` child elements. """ tbl = OxmlElement('w:tbl') tblPr = CT_TblPr.new() tbl.append(tblPr) tblGrid = CT_TblGrid.new() tbl.append(tblGrid) return tbl
def new(cls, pic_id, filename, rId, cx, cy): """ Return a new ``<pic:pic>`` element populated with the minimal contents required to define a viable picture element, based on the values passed as parameters. """ pic = OxmlElement('pic:pic', nsmap=nspfxmap('pic', 'r')) pic.append(CT_PictureNonVisual.new(pic_id, filename)) pic.append(CT_BlipFillProperties.new(rId)) pic.append(CT_ShapeProperties.new(cx, cy)) return pic
def new(cls): """ Return a new ``<w:tbl>`` element, containing the required ``<w:tblPr>`` and ``<w:tblGrid>`` child elements. """ tbl = OxmlElement("w:tbl") tblPr = CT_TblPr.new() tbl.append(tblPr) tblGrid = CT_TblGrid.new() tbl.append(tblGrid) return tbl
def new(cls, num_id, abstractNum_id): """ Return a new ``<w:num>`` element having numId of *num_id* and having a ``<w:abstractNumId>`` child with val attribute set to *abstractNum_id*. """ abstractNumId = CT_DecimalNumber.new( 'w:abstractNumId', abstractNum_id ) num = OxmlElement('w:num', {qn('w:numId'): str(num_id)}) num.append(abstractNumId) return num
def add_hyperlink(paragraph, url, text, color='0000FF', underline=True): """ places a hyperlink within a paragraph object """ part = paragraph.part r_id = part.relate_to(url, RELATIONSHIP_TYPE.HYPERLINK, is_external=True) hyperlink = OxmlElement('w:hyperlink') hyperlink.set( qn('r:id'), r_id, ) new_run = OxmlElement('w:r') rPr = OxmlElement('w:rPr') if color is not None: c = OxmlElement('w:color') c.set(qn('w:val'), color) rPr.append(c) if not underline: u = OxmlElement('w:u') u.set(qn('w:val'), 'none') rPr.append(u) # Join all the xml elements together add add the required text to the w:r element new_run.append(rPr) new_run.text = text hyperlink.append(new_run) paragraph._p.append(hyperlink) return hyperlink
def remove_border(self, row, border=0): for cell in row: tcPr = cell.tcPr tcBorders = OxmlElement('w:tcBorders') top = OxmlElement('w:top') top.set(qn('w:val'), 'nil') left = OxmlElement('w:left') left.set(qn('w:val'), 'nil') bottom = OxmlElement('w:bottom') bottom.set(qn('w:val'), 'nil') bottom.set(qn('w:sz'), '4') bottom.set(qn('w:space'), '0') bottom.set(qn('w:color'), 'auto') right = OxmlElement('w:right') right.set(qn('w:val'), 'nil') if border == 1: tcBorders.append(top) if border == 2: tcBorders.append(bottom) if border == 0: tcBorders.append(top) tcBorders.append(bottom) tcPr.append(tcBorders)
def add_hyperlink(paragraph, text, url): part = paragraph.part r_id = part.relate_to(url, RELATIONSHIP_TYPE.HYPERLINK, is_external=True) hyperlink = OxmlElement("w:hyperlink") hyperlink.set(qn("r:id"), r_id, ) new_run = OxmlElement("w:r") rPr = OxmlElement("w:rPr") new_run.append(rPr) new_run.text = text hyperlink.append(new_run) r = paragraph.add_run() r._r.append(hyperlink) r.font.color.theme_color = MSO_THEME_COLOR_INDEX.HYPERLINK r.font.underline = True return hyperlink
def set_cell_border(cell: _Cell, **kwargs): """ Set the border of a cell. Usage example: set_cell_border(cell, top={"sz": 12, "val": "single", "color": "#FF0000", "space": "0"}, # top border bottom={"sz": 12, "color": "#00FF00", "val": "single"}, # bottom border start={"sz": 24, "val": "dashed", "shadow": "true"}, # left border end={"sz": 12, "val": "dashed"} # right border ) Available attributes can be found here: http://officeopenxml.com/WPtableBorders.php Args: cell: Cell with borders to be changed. """ # access to XML element <w:tc> and its properties tc = cell._tc tcPr = tc.get_or_add_tcPr() # check for tag existence, if none found, then create one tcBorders = tcPr.first_child_found_in("w:tcBorders") if tcBorders is None: tcBorders = OxmlElement('w:tcBorders') tcPr.append(tcBorders) # list over all available tags for edge in ('start', 'top', 'end', 'bottom', 'insideH', 'insideV'): edge_data = kwargs.get(edge) if edge_data: tag = 'w:{}'.format(edge) # check for tag existence, if none found, then create one element = tcBorders.find(qn(tag)) if element is None: element = OxmlElement(tag) tcBorders.append(element) # looks like order of attributes is important for key in ["sz", "val", "color", "space", "shadow"]: if key in edge_data: element.set(qn('w:{}'.format(key)), str(edge_data[key]))
def modifytableborders(table, width, color): tbl = table._tbl # récupération de l'élément XML correspondant à la table for cell in tbl.iter_tcs(): # Pour cahque cellule de la table tcpr = cell.tcPr # récupération de l'élément XML tcpr de la cellule tcborders = OxmlElement( 'w:tcborders') # Création d'un element XML Borders # Pour chaque bordure haut, gauche, bas, droite => Ordre important for tag in ('w:top', 'w:left', 'w:bottom', 'w:right'): element = OxmlElement( tag ) # création d'un element XML correspondant à la bordure en cours element.set(qn('w:sz'), str(width)) # taille de la bordure element.set(qn('w:val'), 'single') # bordure simple element.set(qn('w:color'), color) # couleur de la bordure element.set(qn('w:space'), '0') # espacement entre les bordures tcborders.append( element) # ajout de l'élément à l'élément XML borders tcpr.append(tcborders) # Ajout de l'élément XML BORDERS à la cellule
def __add_image_from_url(self, cell, image_url): margins = ['top', 'start', 'bottom', 'end'] margin_size = '0' tc = cell._tc tcPr = tc.get_or_add_tcPr() tcMar = OxmlElement('w:tcMar') for margin in margins: node = OxmlElement('w:%s' % margin) node.set(qn('w:w'), margin_size) node.set(qn('w:type'), 'dxa') tcMar.append(node) tcPr.append(tcMar) img_width = Mm(40) image = self.__get_image_from_url(image_url) paragraph = cell.paragraphs[0] run = paragraph.add_run() run.add_picture(image, width=img_width) return True
def insertHR(paragraph): p = paragraph._p # p is the <w:p> XML element pPr = p.get_or_add_pPr() pBdr = OxmlElement('w:pBdr') pPr.insert_element_before( pBdr, 'w:shd', 'w:tabs', 'w:suppressAutoHyphens', 'w:kinsoku', 'w:wordWrap', 'w:overflowPunct', 'w:topLinePunct', 'w:autoSpaceDE', 'w:autoSpaceDN', 'w:bidi', 'w:adjustRightInd', 'w:snapToGrid', 'w:spacing', 'w:ind', 'w:contextualSpacing', 'w:mirrorIndents', 'w:suppressOverlap', 'w:jc', 'w:textDirection', 'w:textAlignment', 'w:textboxTightWrap', 'w:outlineLvl', 'w:divId', 'w:cnfStyle', 'w:rPr', 'w:sectPr', 'w:pPrChange') bottom = OxmlElement('w:bottom') bottom.set(qn('w:val'), 'single') bottom.set(qn('w:sz'), '6') bottom.set(qn('w:space'), '1') bottom.set(qn('w:color'), 'auto') pBdr.append(bottom)
def add_hyperlink_into_run(paragraph, run, i, url): runs = paragraph.runs if i is None: for i, runi in enumerate(runs): if run.text == runi.text: break # This gets access to the document.xml.rels file and gets a new relation id value part = paragraph.part r_id = part.relate_to(url, docx.opc.constants.RELATIONSHIP_TYPE.HYPERLINK, is_external=True) # Create the w:hyperlink tag and add needed values hyperlink = OxmlElement('w:hyperlink') hyperlink.set(docx.oxml.shared.qn('r:id'), r_id, ) hyperlink.append(run._r) # see above comment about insert # paragraph._p.insert(i+1, hyperlink) paragraph._p.append(hyperlink) i += 1 while i < len(runs): paragraph._p.append(runs[i]._r) i += 1
def add_hyperlink(paragraph, url, text): """ A function that places a hyperlink within a paragraph object. Parameters ---------- paragraph: The paragraph we are adding the hyperlink to. url: A string containing the required url text: The text displayed for the url Returns ------- hyperlink: hyperlink object """ # This gets access to the document.xml.rels file and gets a new relation id value part = paragraph.part r_id = part.relate_to(url, docx.opc.constants.RELATIONSHIP_TYPE.HYPERLINK, is_external=True) # Create the w:hyperlink tag and add needed values hyperlink = OxmlElement("w:hyperlink") hyperlink.set(qn("r:id"), r_id) # Create a w:r element new_run = OxmlElement("w:r") # Create a new w:rPr element rPr = OxmlElement("w:rPr") # Style it c = OxmlElement("w:color") c.set(qn("w:val"), "4F81BD") rPr.append(c) u = OxmlElement("w:u") u.set(qn("w:val"), "none") rPr.append(u) # Join all the xml elements together add add the required text to the w:r element new_run.append(rPr) new_run.text = text hyperlink.append(new_run) paragraph._p.append(hyperlink) return hyperlink
def add_hyperlink(paragraph, url, text, font_name=None, underline_hyperlink=True, indent=0, color=True): """ A function that places a hyperlink within a paragraph object. :param paragraph: The paragraph we are adding the hyperlink to. :param url: A string containing the required url :param text: The text displayed for the url :return: A Run object containing the hyperlink """ # This gets access to the document.xml.rels file and gets a new relation id value part = paragraph.part r_id = part.relate_to(url, RT.HYPERLINK, is_external=True) # Create the w:hyperlink tag and add needed values hyperlink = OxmlElement('w:hyperlink') hyperlink.set( qn('r:id'), r_id, ) hyperlink.set(qn('w:history'), '1') # Create a w:r element new_run = OxmlElement('w:r') # Create a new w:rPr element rPr = OxmlElement('w:rPr') # Create a w:rStyle element, note this currently does not add the hyperlink style as its not in # the default template, I have left it here in case someone uses one that has the style in it rStyle = OxmlElement('w:rStyle') rStyle.set(qn('w:val'), 'Hyperlink') # Join all the xml elements together add add the required text to the w:r element rPr.append(rStyle) new_run.append(rPr) new_run.text = text hyperlink.append(new_run) # Create a new Run object and add the hyperlink into it r = paragraph.add_run() r.font.name = font_name r._r.append(hyperlink) # A workaround for the lack of a hyperlink style (doesn't go purple after using the link) # Delete this if using a template that has the hyperlink style in it if color: r.font.color.theme_color = MSO_THEME_COLOR_INDEX.HYPERLINK r.font.underline = underline_hyperlink return r
def add_hyperlink(paragraph, url, text, color, underline): """ A function that places a hyperlink within a paragraph object. :param paragraph: The paragraph we are adding the hyperlink to. :param url: A string containing the required url :param text: The text displayed for the url :return: The hyperlink object """ # This gets access to the document.xml.rels file and gets a new relation id value part = paragraph.part r_id = part.relate_to(url, RELATIONSHIP_TYPE.HYPERLINK, is_external=True) # Create the w:hyperlink tag and add needed values hyperlink = OxmlElement('w:hyperlink') hyperlink.set( sharedqn('r:id'), r_id, ) # Create a w:r element new_run = OxmlElement('w:r') # Create a new w:rPr element rPr = OxmlElement('w:rPr') # Add color if it is given if not color is None: c = OxmlElement('w:color') c.set(sharedqn('w:val'), color) rPr.append(c) # Remove underlining if it is requested if not underline: u = OxmlElement('w:u') u.set(sharedqn('w:val'), 'none') rPr.append(u) # Join all the xml elements together add add the required text to the w:r element new_run.append(rPr) new_run.text = text hyperlink.append(new_run) paragraph._p.append(hyperlink) return hyperlink
def new(cls, cx, cy, shape_id, pic): """ Return a new ``<wp:inline>`` element populated with the values passed as parameters. """ name = 'Picture %d' % shape_id uri = nsmap['pic'] inline = OxmlElement('wp:inline', nsmap=nspfxmap('wp', 'r')) inline.append(CT_PositiveSize2D.new('wp:extent', cx, cy)) inline.append(CT_NonVisualDrawingProps.new('wp:docPr', shape_id, name)) inline.append(CT_GraphicalObject.new(uri, pic)) return inline
def add_hyperlink(paragraph, url, text, font="Times New Roman", size=14, color='0000FF', underline=True): # https://github.com/python-openxml/python-docx/issues/74#issuecomment-261169410 # https://github.com/python-openxml/python-docx/issues/383#issue-220027501 part = paragraph.part r_id = part.relate_to(url, RELATIONSHIP_TYPE.HYPERLINK, is_external=True) hyperlink = OxmlElement('w:hyperlink') hyperlink.set( qn('r:id'), r_id, ) new_run = OxmlElement('w:r') rPr = OxmlElement('w:rPr') c = OxmlElement('w:color') c.set(qn('w:val'), color) rPr.append(c) c = OxmlElement('w:sz') c.set(qn('w:val'), str(size * 2)) # size x2 and convert to str (20 == 10) rPr.append(c) c = OxmlElement('w:rFonts') c.set(qn('w:ascii'), font) c.set(qn('w:eastAsia'), font) c.set(qn('w:hAnsi'), font) c.set(qn('w:cs'), font) rPr.append(c) if not underline: u = OxmlElement('w:u') u.set(qn('w:val'), 'none') rPr.append(u) new_run.append(rPr) new_run.text = text hyperlink.append(new_run) paragraph._p.append(hyperlink) return hyperlink
def new(cls, uri, pic): graphicData = OxmlElement('a:graphicData') graphicData.set('uri', uri) graphicData.append(pic) return graphicData
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 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 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))) else: template = 'openvasreporting/src/openvas-template.docx' vuln_info, vuln_levels, vuln_host_by_level, vuln_by_family = _get_collections( vuln_info) # ==================== # DOCUMENT PROPERTIES # ==================== document = Document(template) doc_prop = document.core_properties doc_prop.title = "OpenVAS Report" doc_prop.category = "Report" document.add_paragraph('OpenVAS Report', style='Title') # ==================== # TABLE OF CONTENTS # ==================== document.add_paragraph('Table of Contents', style='Heading 1') par = document.add_paragraph() 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 "OV-H1toc;1;OV-H2toc;2;OV-H3toc;3;OV-Finding;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='OV-H1toc') document.add_paragraph('< TYPE YOUR MANAGEMENT SUMMARY HERE >') document.add_page_break() # ==================== # TECHNICAL FINDINGS # ==================== document.add_paragraph('Technical Findings', style='OV-H1toc') document.add_paragraph( 'The section below discusses the technical findings.') # -------------------- # SUMMARY TABLE # -------------------- document.add_paragraph('Summary', style='OV-H2toc') colors_sum = [] labels_sum = [] vuln_sum = [] aff_sum = [] table_summary = document.add_table(rows=1, cols=3) hdr_cells = table_summary.rows[0].cells hdr_cells[0].paragraphs[0].add_run('Risk level').bold = True hdr_cells[1].paragraphs[0].add_run('Vulns number').bold = True hdr_cells[2].paragraphs[0].add_run('Affected hosts').bold = True # 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]) # -------------------- # CHART # -------------------- fd, path = tempfile.mkstemp(suffix='.png') par_chart = document.add_paragraph() run_chart = par_chart.add_run() plt.figure() pos = np.arange(len(labels_sum)) width = 0.35 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='//') plt.title('Vulnerability summary by risk level') plt.subplot().set_xticks(pos) plt.subplot().set_xticklabels(labels_sum) plt.gca().spines['left'].set_visible(False) plt.gca().spines['right'].set_visible(False) plt.gca().spines['top'].set_visible(False) plt.gca().spines['bottom'].set_position('zero') plt.tick_params(top=False, bottom=True, left=False, right=False, labelleft=False, labelbottom=True) plt.subplots_adjust(left=0.0, right=1.0) def __label_bars(barcontainer): for bar in barcontainer: height = bar.get_height() plt.gca().text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.3, str(int(height)), ha='center', color='black', fontsize=9) __label_bars(bars_vuln) __label_bars(bars_aff) plt.legend() plt.savefig(path) # plt.show() # DEBUG run_chart.add_picture(path, width=Cm(8.0)) plt.figure() values = list(vuln_by_family.values()) pie, tx, autotexts = plt.pie(values, labels=vuln_by_family.keys(), autopct='') plt.title('Vulnerability by family') for i, txt in enumerate(autotexts): txt.set_text('{}'.format(values[i])) plt.axis('equal') plt.savefig( path, bbox_inches='tight' ) # bbox_inches fixes labels being cut, however only on save not on show # plt.show() # DEBUG run_chart.add_picture(path, width=Cm(8.0)) os.close(fd) 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='OV-H2toc').paragraph_format.page_break_before = True cur_level = level else: document.add_page_break() title = "[{}] {}".format(level.upper(), vuln.name) document.add_paragraph(title, style='OV-Finding') table_vuln = document.add_table(rows=8, cols=3) table_vuln.autofit = False # 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) for col_cell in col_cells: col_cell.width = Cm(0.42) # TABLE HEADERS # -------------------- hdr_cells = table_vuln.columns[1].cells hdr_cells[0].paragraphs[0].add_run('Description').bold = True hdr_cells[1].paragraphs[0].add_run('Impact').bold = True hdr_cells[2].paragraphs[0].add_run('Recommendation').bold = True hdr_cells[3].paragraphs[0].add_run('Details').bold = True hdr_cells[4].paragraphs[0].add_run('CVSS').bold = True hdr_cells[5].paragraphs[0].add_run('CVEs').bold = True hdr_cells[6].paragraphs[0].add_run('Family').bold = True hdr_cells[7].paragraphs[0].add_run('References').bold = True for hdr_cell in hdr_cells: hdr_cell.width = Cm(3.58) # 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 txt_cell in txt_cells: txt_cell.width = Cm(12.50) # VULN HOSTS # -------------------- document.add_paragraph('Vulnerable hosts', style='Heading 4') # add coloumn for result per port and resize columns table_hosts = document.add_table(cols=5, rows=(len(vuln.hosts) + 1)) col_cells = table_hosts.columns[1].cells for col_cell in col_cells: col_cell.width = Cm(3.2) col_cells = table_hosts.columns[2].cells for col_cell in col_cells: col_cell.width = Cm(3.2) col_cells = table_hosts.columns[2].cells for col_cell in col_cells: col_cell.width = Cm(1.6) col_cells = table_hosts.columns[3].cells for col_cell in col_cells: col_cell.width = Cm(1.6) col_cells = table_hosts.columns[4].cells for col_cell in col_cells: col_cell.width = Cm(6.4) hdr_cells = table_hosts.rows[0].cells hdr_cells[0].paragraphs[0].add_run('IP').bold = True hdr_cells[1].paragraphs[0].add_run('Host name').bold = True hdr_cells[2].paragraphs[0].add_run('Port number').bold = True hdr_cells[3].paragraphs[0].add_run('Port protocol').bold = True hdr_cells[4].paragraphs[0].add_run('Port result').bold = True 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" document.save(output_file)
def write_report(args, reportDir, lookup, whoisResult, dnsResult, googleResult, shodanResult, pasteScrapeResult, harvesterResult, scrapeResult, credResult, pyfocaResult): today = time.strftime("%m/%d/%Y") for l in lookup: print '[+] Starting OSINT report for ' + l #dump to a word doc #refs #https://python-docx.readthedocs.io/en/latest/user/text.html #https://python-docx.readthedocs.io/en/latest/user/quickstart.html #create a document document = docx.Document() #add logo document.add_picture('./resources/logo.png', height=Inches(1.25)) #add domain cover info paragraph = document.add_paragraph() runParagraph = paragraph.add_run('%s' % l) font = runParagraph.font font.name = 'Arial' font.size = Pt(28) font.color.rgb = RGBColor(0x00, 0x00, 0x00) #add cover info paragraph = document.add_paragraph() runParagraph = paragraph.add_run( 'Open Source Intelligence Report\n\n\n\n\n\n\n\n\n\n\n') font = runParagraph.font font.name = 'Arial' font.size = Pt(26) font.color.rgb = RGBColor(0xe9, 0x58, 0x23) paragraph = document.add_paragraph() runParagraph = paragraph.add_run('Generated on: %s' % today) font = runParagraph.font font.name = 'Arial' font.size = Pt(16) font.color.rgb = RGBColor(0x00, 0x00, 0x00) #page break for cover page document.add_page_break() #add intro text on intropage heading = document.add_heading() runHeading = heading.add_run('Executive Summary') font = runHeading.font font.name = 'Arial' font.size = Pt(20) font.color.rgb = RGBColor(0xe9, 0x58, 0x23) paragraph = document.add_paragraph() runParagraph = paragraph.add_run( '\nThis document contains information about network, technology, and people associated with the assessment targets. The information was obtained by programatically querying various free or low cost Internet data sources.\n' ) font = runParagraph.font font.name = 'Arial' font.size = Pt(11) runParagraph = paragraph.add_run( '\nThese data include information about the network, technology, and people associated with the targets.\n' ) font = runParagraph.font font.name = 'Arial' font.size = Pt(11) runParagraph = paragraph.add_run( '\nSpecific data sources include: whois, domain name system (DNS) records, Google dork results, and data from recent compromises such as LinkedIn. Other sources include results from Shodan, document metadata from theHarvester and pyFoca, as well as queries to Pastebin, Github, job boards, etc. \n' ) font = runParagraph.font font.name = 'Arial' font.size = Pt(11) #page break for cover page document.add_page_break() heading = document.add_heading() runHeading = heading.add_run('Table of Contents') font = runHeading.font font.bold = True font.name = 'Arial' font.size = Pt(20) font.color.rgb = RGBColor(0x0, 0x0, 0x0) #TOC https://github.com/python-openxml/python-docx/issues/36 paragraph = document.add_paragraph() run = paragraph.add_run() font.name = 'Arial' font.size = Pt(11) fldChar = OxmlElement('w:fldChar') # creates a new element fldChar.set(qn('w:fldCharType'), 'begin') # sets attribute on element instrText = OxmlElement('w:instrText') instrText.set(qn('xml:space'), 'preserve') # sets attribute on element instrText.text = 'TOC \o "1-3" \h \z \u' # change 1-3 depending on heading levels you need fldChar2 = OxmlElement('w:fldChar') fldChar2.set(qn('w:fldCharType'), 'separate') fldChar3 = OxmlElement('w:t') fldChar3.text = "Right-click to update field." fldChar2.append(fldChar3) fldChar4 = OxmlElement('w:fldChar') fldChar4.set(qn('w:fldCharType'), 'end') r_element = run._r r_element.append(fldChar) r_element.append(instrText) r_element.append(fldChar2) r_element.append(fldChar4) p_element = paragraph._p #page break for toc document.add_page_break() if credResult: print '[+] Adding credential dump results to report' #header heading = document.add_heading(level=3) runHeading = heading.add_run( 'Credentials found from recent compromises (LinkedIn, Adobe, etc.) related to: %s' % l) font = runHeading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) paragraph = document.add_paragraph() for c in credResult: runParagraph = paragraph.add_run(''.join(c)) font = runParagraph.font font.name = 'Arial' font.size = Pt(11) document.add_page_break() #add whois data with header and break after end if whoisResult: print '[+] Adding whois results to report' #header heading = document.add_heading(level=3) runHeading = heading.add_run('Whois Data for: %s' % l) font = runHeading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) #content paragraph = document.add_paragraph() for w in whoisResult: runParagraph = paragraph.add_run('\n'.join(w)) font = runParagraph.font font.name = 'Arial' font.size = Pt(10) document.add_page_break() #add dns data with header and break after end if dnsResult: print '[+] Adding DNS results to report' #header heading = document.add_heading(level=3) runHeading = heading.add_run('Domain Name System Data for: %s' % l) font = runHeading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) #content paragraph = document.add_paragraph() for d in dnsResult: runParagraph = paragraph.add_run('\n'.join(d)) font = runParagraph.font font.name = 'Arial' font.size = Pt(10) document.add_page_break() #google dork output if googleResult: print '[+] Adding google dork results to report' #header heading = document.add_heading(level=3) runHeading = heading.add_run('Google Dork Results for: %s' % l) font = runHeading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) #content paragraph = document.add_paragraph() for r in googleResult: runParagraph = paragraph.add_run(''.join(r + '\n')) font = runParagraph.font font.name = 'Arial' font.size = Pt(10) document.add_page_break() #harvester output if harvesterResult: print '[+] Adding theHarvester results to report' #header heading = document.add_heading(level=3) runHeading = heading.add_run('theHarvester Results for: %s' % l) font = runHeading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) #content paragraph = document.add_paragraph() for h in harvesterResult: runParagraph = paragraph.add_run(''.join(h)) #set font stuff font = runParagraph.font font.name = 'Arial' font.size = Pt(10) document.add_page_break() #pastebin scrape output if pasteScrapeResult: print '[+] Adding pastebin scrape results to report' document.add_heading('Pastebin URLs for %s' % l, level=3) document.add_paragraph(pasteScrapeResult) document.add_page_break() #document.add_paragraph(pasteScrapeContent) #document.add_page_break() #general scrape output if scrapeResult: print '[+] Adding website scraping results to report' #header heading = document.add_heading(level=3) runHeading = heading.add_run('Website Scraping Results for %s' % l) font = runHeading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) #content paragraph = document.add_paragraph() for sr in scrapeResult: runParagraph = paragraph.add_run(sr) font = runParagraph.font font.name = 'Arial' font.size = Pt(10) document.add_page_break() #pyfoca results if pyfocaResult: print '[+] Adding pyfoca results to report' heading = document.add_heading(level=3) runHeading = heading.add_run('pyFoca Results for: %s' % l) font = runHeading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) paragraph = document.add_paragraph() for fr in pyfocaResult: #lolwut runParagraph = paragraph.add_run(''.join( str(fr).strip(("\\ba\x00b\n\rc\fd\xc3")))) font = runParagraph.font font.name = 'Arial' font.size = Pt(10) document.add_page_break() #shodan output if shodanResult: heading = document.add_heading(level=3) runHeading = heading.add_run('Shodan Results for: %s' % l) font = runHeading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) paragraph = document.add_paragraph() for shr in shodanResult: try: runParagraph = paragraph.add_run( str(shr).strip(("\\ba\x00b\n\rc\fd\xc3"))) #set font stuff font = runParagraph.font font.name = 'Arial' font.size = Pt(10) except: print 'probably an encoding error...' continue print '[+] Writing file: ./reports/%s/OSINT_%s_.docx' % (l, l) document.save(reportDir + l + '/' + l + 'OSINT_%s_.docx' % l)
def Remove_Border(self, row, border=0, outside=False): for cell in row: tcPr = cell.tcPr tcBorders = OxmlElement('w:tcBorders') top = OxmlElement('w:top') top.set(qn('w:val'), 'nil') bottom = OxmlElement('w:bottom') bottom.set(qn('w:val'), 'nil') bottom.set(qn('w:sz'), '4') bottom.set(qn('w:space'), '0') bottom.set(qn('w:color'), 'auto') right = OxmlElement('w:right') right.set(qn('w:val'), 'nil') if border == 1: tcBorders.append(top) if border == 2: tcBorders.append(bottom) if border == 0: tcBorders.append(top) tcBorders.append(bottom) if border == 3: tcBorders.append(right) tcBorders.append(left) tcBorders.append(top) tcBorders.append(bottom) tcPr.append(tcBorders) index = 0 if outside == True: for cell in row: tcPr = cell.tcPr tcBorders = OxmlElement('w:tcBorders') left = OxmlElement('w:left') left.set(qn('w:val'), 'nil') right = OxmlElement('w:right') right.set(qn('w:val'), 'nil') if index == 0: tcBorders.append(left) if row.index(cell) == row.index(row[-1]): tcBorders.append(right) tcPr.append(tcBorders) index += 1
def run(self, \ args, \ report_directory, \ lookup, whois_result, \ dns_result, \ google_result, \ shodan_result, \ paste_scrape_result, \ theharvester_result, \ webscrape_result, \ cred_result, \ pyfoca_result): for l in lookup: print('[+] Starting OSINT report for '.format(l)) self.document = docx.Document() #add logo self.document.add_picture('./resources/logo.png', height=Inches(1.25)) #add domain cover info paragraph = self.document.add_paragraph() run_paragraph = paragraph.add_run('%s' % l) font = run_paragraph.font font.name = 'Arial' font.size = Pt(28) font.color.rgb = RGBColor(0x00, 0x00, 0x00) #add cover info paragraph = self.document.add_paragraph() run_paragraph = paragraph.add_run( 'Open Source Intelligence Report\n\n\n\n\n\n\n\n\n\n\n') font = run_paragraph.font font.name = 'Arial' font.size = Pt(26) font.color.rgb = RGBColor(0xe9, 0x58, 0x23) paragraph = self.document.add_paragraph() run_paragraph = paragraph.add_run('Generated on: %s' % self.today) font = run_paragraph.font font.name = 'Arial' font.size = Pt(16) font.color.rgb = RGBColor(0x00, 0x00, 0x00) #page break for cover page self.document.add_page_break() #add intro text on intropage heading = self.document.add_heading() run_heading = heading.add_run('Executive Summary') font = run_heading.font font.name = 'Arial' font.size = Pt(20) font.color.rgb = RGBColor(0xe9, 0x58, 0x23) paragraph = self.document.add_paragraph() run_paragraph = paragraph.add_run( '\nThis document contains information about network, technology, and people associated with the assessment targets. The information was obtained by programatically querying various free or low cost Internet data sources.\n' ) font = run_paragraph.font font.name = 'Arial' font.size = Pt(11) run_paragraph = paragraph.add_run( '\nThese data include information about the network, technology, and people associated with the targets.\n' ) font = run_paragraph.font font.name = 'Arial' font.size = Pt(11) run_paragraph = paragraph.add_run( '\nSpecific data sources include: whois, domain name system (DNS) records, Google dork results, and data from recent compromises such as LinkedIn. Other sources include results from Shodan, document metadata from theHarvester and pyFoca, as well as queries to Pastebin, Github, job boards, etc. \n' ) font = run_paragraph.font font.name = 'Arial' font.size = Pt(11) #page break for cover page self.document.add_page_break() heading = self.document.add_heading() run_heading = heading.add_run('Table of Contents') font = run_heading.font font.bold = True font.name = 'Arial' font.size = Pt(20) font.color.rgb = RGBColor(0x0, 0x0, 0x0) #TOC https://github.com/python-openxml/python-docx/issues/36 paragraph = self.document.add_paragraph() run = paragraph.add_run() font.name = 'Arial' font.size = Pt(11) fldChar = OxmlElement('w:fldChar') # creates a new element fldChar.set(qn('w:fldCharType'), 'begin') # sets attribute on element instrText = OxmlElement('w:instrText') instrText.set(qn('xml:space'), 'preserve') # sets attribute on element instrText.text = 'TOC \o "1-3" \h \z \u' # change 1-3 depending on heading levels you need fldChar2 = OxmlElement('w:fldChar') fldChar2.set(qn('w:fldCharType'), 'separate') fldChar3 = OxmlElement('w:t') fldChar3.text = "Right-click to update field." fldChar2.append(fldChar3) fldChar4 = OxmlElement('w:fldChar') fldChar4.set(qn('w:fldCharType'), 'end') r_element = run._r r_element.append(fldChar) r_element.append(instrText) r_element.append(fldChar2) r_element.append(fldChar4) p_element = paragraph._p #page break for toc self.document.add_page_break() if cred_result is not None: print('[+] Adding credential dump results to report') #header heading = self.document.add_heading(level=3) run_heading = heading.add_run( 'Credentials found from recent compromises (LinkedIn, Adobe, etc.) related to: %s' % l) font = run_heading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) paragraph = self.document.add_paragraph() for c in cred_result: run_paragraph = paragraph.add_run(''.join(c)) font = run_paragraph.font font.name = 'Arial' font.size = Pt(11) self.document.add_page_break() #add whois data with header and break after end if whois_result is not None: print('[+] Adding whois results to report') #header heading = self.document.add_heading(level=3) run_heading = heading.add_run('Whois Data for: %s' % l) font = run_heading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) #content paragraph = self.document.add_paragraph() for line in whois_result: if ':' in line: run_paragraph = paragraph.add_run(''.join(line) + '\n') font = run_paragraph.font font.name = 'Arial' font.size = Pt(10) self.document.add_page_break() #add dns data with header and break after end if dns_result is not None: print('[+] Adding DNS results to report') #header heading = self.document.add_heading(level=3) run_heading = heading.add_run( 'Domain Name System Data for: %s' % l) font = run_heading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) #content paragraph = self.document.add_paragraph() for d in dns_result: run_paragraph = paragraph.add_run('\n'.join(d)) font = run_paragraph.font font.name = 'Arial' font.size = Pt(10) self.document.add_page_break() #google dork output if google_result is not None: print('[+] Adding google dork results to report') #header heading = self.document.add_heading(level=3) run_heading = heading.add_run('Google Dork Results for: %s' % l) font = run_heading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) #content paragraph = self.document.add_paragraph() for r in google_result: run_paragraph = paragraph.add_run(''.join(r + '\n')) font = run_paragraph.font font.name = 'Arial' font.size = Pt(10) self.document.add_page_break() #harvester output if theharvester_result is not None: print('[+] Adding theHarvester results to report') #header heading = self.document.add_heading(level=3) run_heading = heading.add_run('theHarvester Results for: %s' % l) font = run_heading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) #content paragraph = self.document.add_paragraph() for h in theharvester_result: run_paragraph = paragraph.add_run(''.join(h)) #set font stuff font = run_paragraph.font font.name = 'Arial' font.size = Pt(10) self.document.add_page_break() #pastebin scrape output if paste_scrape_result is not None: print('[+] Adding pastebin scrape results to report') heading = self.document.add_heading(level=3) run_heading = heading.add_run('Pastebin URLs for %s' % l) font = run_heading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) paragraph = self.document.add_paragraph() self.document.add_paragraph(paste_scrape_result) font = run_paragraph.font font.name = 'Arial' font.size = Pt(10) self.document.add_page_break() #general scrape output if webscrape_result is not None: print('[+] Adding website scraping results to report') #header heading = self.document.add_heading(level=3) run_heading = heading.add_run( 'Website Scraping Results for %s' % l) font = run_heading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) #content paragraph = self.document.add_paragraph() for sr in webscrape_result: for line in sr: run_paragraph = paragraph.add_run(line) font = run_paragraph.font font.name = 'Arial' font.size = Pt(10) self.document.add_page_break() #pyfoca results if pyfoca_result is not None: print('[+] Adding pyfoca results to report') heading = self.document.add_heading(level=3) run_heading = heading.add_run('pyFoca Results for: %s' % l) font = run_heading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) paragraph = self.document.add_paragraph() for fr in pyfoca_result: run_paragraph = paragraph.add_run(''.join( str(fr).strip(("\\ba\x00b\n\rc\fd\xc3")))) font = run_paragraph.font font.name = 'Arial' font.size = Pt(10) self.document.add_page_break() #shodan output if shodan_result is not None: heading = self.document.add_heading(level=3) run_heading = heading.add_run('Shodan Results for: %s' % l) font = run_heading.font font.name = 'Arial' font.color.rgb = RGBColor(0xe9, 0x58, 0x23) paragraph = self.document.add_paragraph() for shr in shodan_result: try: run_paragraph = paragraph.add_run( str(shr).strip(("\\ba\x00b\n\rc\fd\xc3"))) #set font stuff font = run_paragraph.font font.name = 'Arial' font.size = Pt(10) except: print('probably an encoding error...') continue print('[+] Writing file: ./reports/{}/OSINT_{}_.docx'.format(l, l)) #saves to ./reports/domain.com/OSINT_domain.com_.docx self.document.save(report_directory + '{}/OSINT_{}_.docx'.format(l, l))