import docx import operator import warnings import urllib.request import ssl import re from ast import Str from collections.abc import Callable from xmlrpc.client import Boolean import valispace from PIL import Image from docx import Document from docx.shared import Inches from mailmerge import MailMerge from natsort import natsorted from io import BytesIO import datetime from docx.oxml.shared import qn from htmldocx import HtmlToDocx from docx.oxml.shared import OxmlElement from lxml import etree import copy from .settings import Username, Password #need to setup the secrets within the deployment to use this. If not use the username and password. Refer the video tutorial #Username = "Your_Username" #Password = "Your_password" Export_Specification = "Spacecraft_Spec" #Exact name of the specificaiton that you would like to export Export_Template_Id = 167 #download the template and upload it in the file management and add the ID of the file here Export_Project = 24 #Project id here Export_Document_Name = "Spec Export Document" #Name of the document that you would like to create Export_Custom_Column_One = "AllocatedTo" Export_Custom_Column_Two = "Protocol Multi" Export_Requirement_States = "All" Export_Create_Links = "True" Export_Remove_Empty_Fields = "False" Export_Create_Index = "False" CURRENT_SPECIFICATION = { "id": 0, "name": "", "section": 0, "specs_folder": "", } DEFAULT_VALUES = { "domain": "https://YourdeploymentName.valispace.com/", #Replace "YourdeploymentName" with the deployment you need to log in "states_to_consider": [] } def get_bookmark_par_element(document, bookmark_name): """ Return the named bookmark parent paragraph element. If no matching bookmark is found, the result is '1'. If an error is encountered, '2' is returned. """ doc_element = document.part.element bookmarks_list = doc_element.findall('.//' + qn('w:bookmarkStart')) for bookmark in bookmarks_list: name = bookmark.get(qn('w:name')) if name == bookmark_name: par = bookmark.getparent() if not isinstance(par, docx.oxml.CT_P): return 2 else: return par return 1 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, docx.opc.constants.RELATIONSHIP_TYPE.HYPERLINK, is_external=True) # Create the w:hyperlink tag and add needed values hyperlink = docx.oxml.shared.OxmlElement('w:hyperlink') hyperlink.set(docx.oxml.shared.qn('r:id'), r_id, ) # Create a w:r element new_run = docx.oxml.shared.OxmlElement('w:r') # Create a new w:rPr element rPr = docx.oxml.shared.OxmlElement('w:rPr') # Add color if it is given if not color is None: c = docx.oxml.shared.OxmlElement('w:color') c.set(docx.oxml.shared.qn('w:val'), color) rPr.append(c) # Remove underlining if it is requested if not underline: u = docx.oxml.shared.OxmlElement('w:u') u.set(docx.oxml.shared.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 create_reference_documents_index(document,all_specification_requirements, all_project_files): table = document.add_table(rows=1, cols=3, style="Style1") count = 0 tableHasContent = False for requirement in all_specification_requirements: count += 1 todeletefiles = [] for requirement in all_specification_requirements: todeletefiles = [] for file in all_project_files: if all_project_files[file]['object_id'] == requirement: count += 1 if all_project_files[file]['file_type'] == 1: ref_file = all_project_files[file] else: ref_file = all_project_files[all_project_files[file]['reference_file']] #it's external file ref tableHasContent = True todeletefiles.append(file) if count == 1: heading_row = table.rows[0].cells heading_row[0].text = "FILE NAME" heading_row[1].text = "FILE VERSION" heading_row[2].text = "FILE REFERENCE" data_row = table.add_row().cells data_row[0].text = ref_file['name'] if ref_file['current_version'] != None: data_row[1].text = str(ref_file['current_version']) else: data_row[1].text = "" if ref_file['reference'] != None: data_row[2].text = ref_file['reference'][3:-4] #we remove by "hand" the

and

else: data_row[2].text = "" else: data_row = table.add_row().cells data_row[0].text = ref_file['name'] if ref_file['current_version'] != None: data_row[1].text = str(ref_file['current_version']) else: data_row[1].text = "" if ref_file['reference'] != None: data_row[2].text = ref_file['reference'][3:-4] #we remove by "hand" the

and

else: data_row[2].text = "" for todeletefile in todeletefiles: del all_project_files[todeletefile] if tableHasContent : regexp = re.compile(CURRENT_SPECIFICATION["name"]) for paragraph in document.paragraphs: if paragraph.text and regexp.search(paragraph.text): tbl, p = table._tbl, paragraph._p p.addnext(tbl) #moves index table from end of document to after the Specification Name tbl.addnext(p) #moves index table before the Specification Name else: table._element.getparent().remove(table._element) return def put_url(document, req_url): #We copy all the runs (with format) of the temporary docx with requirement text formated from html conversion into the main document for docx_url_id in req_url: for tables in document.tables: for celda in tables._cells: for p in celda.paragraphs: breakfor = False if docx_url_id in p.text: hyperlink = add_hyperlink(p, req_url[docx_url_id], p.text[:-4], None, True) breakfor = True #nexet we remove the placeholders for run in reversed(list(p.runs)): p._p.remove(run._r) if breakfor: break if breakfor: break def keep_tables_on_one_page(doc): allTables = doc.tables for activeTable in allTables: if activeTable.cell(0,0).paragraphs[0].text == 'REMOVE_TABLE': activeTable._element.getparent().remove(activeTable._element) tags = doc.element.xpath('//w:tr[position() < last()]/w:tc/w:p') for tag in tags: ppr = tag.get_or_add_pPr() ppr.keepNext_val = True tags = doc.element.xpath('//w:tr') rows = len(tags) for row in range(0, rows): tag = tags[row] # Specify which tag you want child = OxmlElement('w:cantSplit') # Create arbitrary tag tag.append(child) # Append in the new tag def adjust_headings(document): pattern = "^\d+(?:\.\d+)*" for paragraph in document.paragraphs: if paragraph.style.name == "Title" and paragraph.text =='': #removes empty headings p = paragraph._element p.getparent().remove(p) p._p = p._element = None elif paragraph.style.name == "Heading 1": #Assuming Section are alway in format "number.number.number section title" clean_header_array = re.split(pattern,paragraph.text) if paragraph.text =='': #removes empty headings p = paragraph._element p.getparent().remove(p) p._p = p._element = None continue elif paragraph.text.count(".") == 0: paragraph.text = paragraph.text[2:] elif paragraph.text.count(".") == 1: if len(clean_header_array)>1: paragraph.text = clean_header_array[1][1:] paragraph.style = "Heading 2" elif paragraph.text.count(".") == 2: if len(clean_header_array)>1: paragraph.text = clean_header_array[1][1:] paragraph.style = "Heading 3" elif paragraph.text.count(".") == 3: if len(clean_header_array)>1: paragraph.text = clean_header_array[1][1:] paragraph.style = "Heading 4" elif paragraph.text.count(".") == 4: if len(clean_header_array)>1: paragraph.text = clean_header_array[1][1:] paragraph.style = "Heading 5" elif paragraph.text.count(".") == 5: if len(clean_header_array)>1: paragraph.text = clean_header_array[1][1:] paragraph.style = "Heading 6" elif paragraph.text.count(".") == 6: if len(clean_header_array)>1: paragraph.text = clean_header_array[1][1:] paragraph.style = "Heading 7" elif paragraph.text.count(".") == 7: if len(clean_header_array)>1: paragraph.text = clean_header_array[1][1:] paragraph.style = "Heading 8" def remove_all_but_last_section(document): sectPrs = document._element.xpath(".//w:pPr/w:sectPr") for sectPr in sectPrs: sectPr.getparent().remove(sectPr) return def get_requirements_without_section(all_spec_requirements): unsorted_section_requirements = {} no_section_requirements = {} for requirement in all_spec_requirements: if all_spec_requirements[requirement]['group'] == None: unsorted_section_requirements[requirement] = all_spec_requirements[requirement] sorted_requirements = natsorted(unsorted_section_requirements.items(), key=lambda x:(operator.getitem(x[1],'identifier').replace(". "," "))) for sorted_ in sorted_requirements: no_section_requirements[sorted_[0]] = sorted_[1] return no_section_requirements def get_section_requirements(all_specification_requirements,section): unsorted_section_requirements = {} section_requirements = {} for requirement in all_specification_requirements: if all_specification_requirements[requirement]['group'] == section: unsorted_section_requirements[requirement] = all_specification_requirements[requirement] sorted_requirements_by_section = natsorted(unsorted_section_requirements.items(), key=lambda x:(operator.getitem(x[1],'identifier').replace(". "," "))) for sorted_ in sorted_requirements_by_section: section_requirements[sorted_[0]] = sorted_[1] return section_requirements def get_specification_sections(api, project): specification_sections = {} all_specifications_groups = get_map(api, f"requirements/groups/?project={project}", "id", None, filter_specification) pre_sorted_section_groups = natsorted(all_specifications_groups.items(), key=lambda x:(operator.getitem(x[1],'name').replace(". "," "))) for pre_sorted in pre_sorted_section_groups: specification_sections[pre_sorted[0]] = pre_sorted[1] return specification_sections def put_images_and_remove_empty_fields(document, all_project_images): for paragraph in document.paragraphs: if "Images_Placeholder" in paragraph.text: #still needs testing/refined. Currently only working/tested for tables! req_images = get_requirement_images(requirement_id,all_project_images) for image in req_images: image_data = Image.open(BytesIO(urllib.request.urlopen(req_images[image]['download_url'], context=ssl.SSLContext()).read())) run = paragraph.add_run() if image_data.info['dpi'][0] == 0: # Open the image using Pillow with Image.open(image_data.fp) as im: # Convert the image to RGB mode if it's not if im.mode != 'RGB': im = im.convert('RGB') # Save the image to a BytesIO object, setting the DPI to a non-zero value image_bytes = BytesIO() im.save(image_bytes, format='JPEG', dpi=(96, 96)) # Set your desired DPI here image_bytes.seek(0) # Add the image to the docx file run.add_picture(image_bytes, width=Inches(6)) elif image_data.width/image_data.info['dpi'][0] > celda.width.inches: run.add_picture(image_data.fp, width=Inches(6)) else: run.add_picture(image_data.fp) for run in reversed(list(paragraph.runs)[:-len(req_images)]): paragraph._p.remove(run._r) elif "No_Images" in paragraph.text: p_el = paragraph._element p_el.getparent().remove(p_el) paragraph._p = paragraph._element = None elif "[TO_DELETE]" in paragraph.text: p_el = paragraph._element p_el.getparent().remove(p_el) paragraph._p = paragraph._element = None elif paragraph.text == "": p_el = paragraph._element if isinstance(p_el.getprevious() , docx.oxml.CT_P): p_el.getparent().remove(p_el) paragraph._p = paragraph._element = None for tables in document.tables: for celda in tables._cells: hasImages = False requirement_id = 0 for p in celda.paragraphs: if "Images_Placeholder" in p.text: requirement_id = int(p.text[19:]) hasImages = True p_el = p._element p_el.getparent().remove(p_el) p._p = p._element = None elif "No_Images" in p.text: p_el = p._element p_el.getparent().remove(p_el) p._p = p._element = None elif "[TO_DELETE]" in p.text: c_el = celda._element row_el = c_el.getparent() top_parent = row_el.getparent() if top_parent != None: top_parent.remove(row_el) else: row_el.remove(c_el) if hasImages: tables.autofit = False new_image_p = celda.add_paragraph() req_images = get_requirement_images(requirement_id,all_project_images) for image in req_images: image_data = Image.open(BytesIO(urllib.request.urlopen(req_images[image]['download_url'], context=ssl.SSLContext()).read())) run = new_image_p.add_run() if image_data.info['dpi'][0] == 0: # Open the image using Pillow with Image.open(image_data.fp) as im: # Convert the image to RGB mode if it's not if im.mode != 'RGB': im = im.convert('RGB') # Save the image to a BytesIO object, setting the DPI to a non-zero value image_bytes = BytesIO() im.save(image_bytes, format='JPEG', dpi=(96, 96)) # Set your desired DPI here image_bytes.seek(0) # Add the image to the docx file run.add_picture(image_bytes, width=Inches(6)) elif image_data.width/image_data.info['dpi'][0] > celda.width.inches: run.add_picture(image_data.fp, width=Inches(6)) else: run.add_picture(image_data.fp) for run in reversed(list(new_image_p.runs)[:-len(req_images)]): new_image_p._p.remove(run._r) def clone_run_props(tmpl_run, this_run, original_run): this_run.bold = tmpl_run.bold this_run.italic = tmpl_run.italic this_run.underline = tmpl_run.underline this_run.font.color.rgb = tmpl_run.font.color.rgb this_run.font.highlight_color = tmpl_run.font.highlight_color this_run.font.strike = tmpl_run.font.strike this_run.font.name = original_run.font.name this_run.font.size = original_run.font.size def is_table_parent(table, paragraph_text): parent = table._element.getprevious() ct_p_xml = etree.tostring(parent, encoding="unicode") xml_tree = etree.fromstring(ct_p_xml) text_content = "" for run in xml_tree.findall(".//w:r", namespaces={"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}): for text_node in run.findall(".//w:t", namespaces={"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}): text_content += text_node.text return paragraph_text == text_content def put_html_text(document, docx_list): #We copy all the runs (with format) of the temporary docx with requirement text formated from html conversion into the main document for docx_html_id in docx_list: for tables in document.tables: for celda in tables._cells: for p in celda.paragraphs: breakfor = False if docx_html_id in p.text: for html_paragraph in docx_list[docx_html_id].paragraphs: if html_paragraph.style.name == 'List Bullet': new_paragraph = celda.add_paragraph(style='BulletList') elif html_paragraph.style.name == 'List Number': new_paragraph = celda.add_paragraph(style='NumberList') else: new_paragraph = celda.add_paragraph(style=p.style) for run in html_paragraph.runs: if run.text != '': cloned_run = new_paragraph.add_run() clone_run_props(run, cloned_run, p.runs[0]) cloned_run.text = run.text for table in docx_list[docx_html_id].tables: if is_table_parent(table, html_paragraph.text): tbl = table._tbl new_tbl = copy.deepcopy(tbl) new_paragraph._p.addnext(new_tbl) breakfor = True #nexet we remove the placeholders p_el = p._element p_el.getparent().remove(p_el) p._p = p._element = None if breakfor: break if breakfor: break for paragraph in document.paragraphs: if docx_html_id in paragraph.text: print(paragraph.text) for html_paragraph in docx_list[docx_html_id].paragraphs: print(html_paragraph) if html_paragraph.style.name == 'List Bullet': new_paragraph = paragraph.insert_paragraph_before(style='BulletList') elif html_paragraph.style.name == 'List Number': new_paragraph = paragraph.insert_paragraph_before(style='NumberList') else: new_paragraph = paragraph.insert_paragraph_before(style=p.style) for run in html_paragraph.runs: if run.text != '': cloned_run = new_paragraph.add_run() clone_run_props(run, cloned_run, p.runs[0]) cloned_run.text = run.text for table in docx_list[docx_html_id].tables: if is_table_parent(table, html_paragraph.text): tbl = table._tbl new_tbl = copy.deepcopy(tbl) new_paragraph._p.addnext(new_tbl) p_el = paragraph._element p_el.getparent().remove(p_el) paragraph._p = paragraph._element = None def filter_specification(element): if element['specification'] == CURRENT_SPECIFICATION["id"]: return True return False def filter_images(file): if file['mimetype'] == 'image/jpeg' or file['mimetype'] == 'image/png': return True return False def filter_not_images(file): if file['mimetype'] != 'image/jpeg' and file['mimetype'] != 'image/png': return True return False def has_images(requirement,images): for objectid in images: if images[objectid]['object_id'] == requirement: return True return False def get_requirement_images(requirement,images): requirement_images = {} for image in images: if images[image]['object_id'] == requirement: requirement_images[image] = images[image] return requirement_images def get_requirement_applicability(requirement,all_project_component_types, all_project_applicability_conditions): all_requirement_applicabilities = "" for applicability in all_project_applicability_conditions: if all_project_applicability_conditions[applicability]['requirement'] == requirement: counter = 0 for component_types in all_project_applicability_conditions[applicability]['component_types']: if len(all_project_applicability_conditions[applicability]['component_types']) == 1: all_requirement_applicabilities+=all_project_component_types.get(component_types)['name'] elif counter == 0: all_requirement_applicabilities+=all_project_component_types.get(component_types)['name'] else: all_requirement_applicabilities+="|"+all_project_component_types.get(component_types)['name'] counter+= 1 all_requirement_applicabilities+=" ; " if (len(all_requirement_applicabilities)) > 1: all_requirement_applicabilities = all_requirement_applicabilities[:-3] return all_requirement_applicabilities def get_requirement_verification_methods(verification_methods): requirement_vms = "" if verification_methods == None: return requirement_vms else: for vm in verification_methods: if vm['method'] != None: requirement_vms+= vm['method']['name'] requirement_vms+=" ; " if (len(verification_methods)) > 0: requirement_vms = requirement_vms[:-3] return requirement_vms def get_requirement_verification_methods_newline(verification_methods): requirement_vms = "" if verification_methods == None: return requirement_vms else: for vm in verification_methods: if vm['method'] != None: requirement_vms+= vm['method']['name'] requirement_vms+=" \n\n " if (len(verification_methods)) > 0: requirement_vms = requirement_vms[:-3] return requirement_vms def get_requirement_verification_methods_components_newline(verification_methods,all_project_components): requirement_vms_components = "" if verification_methods == None: return requirement_vms_components else: for vm in verification_methods: if vm['component_vms'] != None: for component_vms in vm['component_vms']: if component_vms['component'] != None: requirement_vms_components+= all_project_components[component_vms['component']]['name'] requirement_vms_components+=" \n\n " if (len(verification_methods)) > 0: requirement_vms_components = requirement_vms_components[:-3] return requirement_vms_components def get_requirement_verification_methods_status_newline(verification_methods): requirement_vms_status = "" if verification_methods == None: return requirement_vms_status else: for vm in verification_methods: if vm['component_vms'] != None: for component_vms in vm['component_vms']: if component_vms['status'] != None: requirement_vms_status+= component_vms['status'] requirement_vms_status+=" \n\n " if (len(verification_methods)) > 0: requirement_vms_status = requirement_vms_status[:-3] return requirement_vms_status def get_requirement_verification_methods_comments(verification_methods): requirement_vms = "" if verification_methods == None: return requirement_vms else: for vm in verification_methods: if vm['component_vms'] != None: for component_vms in vm['component_vms']: if component_vms['comment'] != None: requirement_vms+= component_vms['comment'][3:-4] #hardcode remove of

and

while clean_text is not available for this field requirement_vms+=" \n\n " if (len(verification_methods)) > 0: requirement_vms = requirement_vms[:-3] return requirement_vms def get_requirement_verification_methods_text(verification_methods,all_project_requirement_vms): requirement_vms_text = "" if verification_methods == None: return requirement_vms_text else: for vm in verification_methods: if vm['text'] != None: requirement_vms_text+= all_project_requirement_vms[vm['id']]['text'][3:-4] #hardcode remove of

and

while clean_text is not available for this field if (len(verification_methods)) > 0: requirement_vms_text = requirement_vms_text[:-3] return requirement_vms_text def get_requirement_verification_methods_and_text(requirement,all_project_requirement_vms,all_project_requirement_ver_methods): requirement_vms = "" for req_vms_id in list(all_project_requirement_vms): req_vms = all_project_requirement_vms[req_vms_id] if req_vms['requirement'] == requirement: if req_vms['text'] != None: vm_method_and_text = req_vms['text'][:3] + all_project_requirement_ver_methods[req_vms['method']]['name'] +': ' + req_vms['text'][3:] requirement_vms+= vm_method_and_text all_project_requirement_vms.pop(req_vms_id) return requirement_vms def get_requirement_attachments_references(requirement,files): requirement_attachments = "" for file in files: if files[file]['object_id'] == requirement: ref_file = files[file] if ref_file['file_type'] == 1: if ref_file['reference'] != None: requirement_attachments+= ref_file['name']+" - "+ref_file['reference'] #" - "+ref_file['version'] #for the future when we also upload the file versiopn and not controled by VS else : requirement_attachments+= ref_file['name'] #+" - "+ref_file['reference'] #" - "+ref_file['version'] #for the future when we also upload the file versiopn and not controled by VS elif ref_file['file_type'] == 3: original_ref_file = files[ref_file['reference_file']] if original_ref_file['reference'] != None: requirement_attachments+= original_ref_file['name']+" - "+original_ref_file['reference'] #" - "+original_ref_file['version'] #for the future when we also upload the file versiopn and not controled by VS else : requirement_attachments+= original_ref_file['name'] #+" - "+original_ref_file['reference'] #" - "+original_ref_file['version'] #for the future when we also upload the file versiopn and not controled by VS requirement_attachments+=" ; " if (len(requirement_attachments)) > 0: requirement_attachments = requirement_attachments[:-3] return requirement_attachments def get_requirement_type(requirement,all_project_requirement_types): for req_types in all_project_requirement_types: if all_project_requirement_types[req_types]['id'] == requirement['type']: return all_project_requirement_types[req_types]['name'] return "" def get_requirement_tags(req_tags): tags = "" if req_tags == None: return tags else: for tag in req_tags: tags+= tag tags+=" ; " if (len(req_tags)) > 0: tags = tags[:-3] return tags def get_requirement_owner(owner,all_deployment_users, all_deployment_user_groups): #It will get the User groups (removig Internal and External) + the user First and Last Names req_owner = "" isSingleOwner = True if owner == None: return req_owner else: req_owner_id = owner['id'] if owner['contenttype'] == 4 : isSingleOwner = False if isSingleOwner: req_owner = all_deployment_users[req_owner_id]['first_name']+ " " + all_deployment_users[req_owner_id]['last_name'] req_owner_groups = "" if len(all_deployment_users[req_owner_id]['groups'])>0: for user_groups in all_deployment_users[req_owner_id]['groups']: if all_deployment_user_groups[user_groups]['name'] != "Internal" and all_deployment_user_groups[user_groups]['name'] != "External": req_owner_groups+= all_deployment_user_groups[user_groups]['name'] req_owner_groups+=" / " if (len(req_owner_groups)) > 0: req_owner_groups = req_owner_groups[:-3] req_owner = req_owner_groups + " -> " + req_owner else: req_owner = all_deployment_user_groups[req_owner_id]['name'] return req_owner def get_requirement_custom_field(requirement,all_project_custom_fields,all_project_custom_field_options,all_project_custom_field_rows,field_name): custom_field_value = "" custom_field = all_project_custom_fields.get(field_name) if custom_field == None : return custom_field_value custom_field_id = custom_field['id'] req_custom_fields = requirement['custom_field_rows'] if len(req_custom_fields) > 0 : for custom_field_row in req_custom_fields: if custom_field_row['custom_field'] == custom_field_id: if all_project_custom_fields[field_name]['type'] == 0: custom_field_value = custom_field_row['value'] #custom_field_value = custom_field_value[3:-4] #to remove the

and

elif all_project_custom_fields[field_name]['type'] == 1: if all_project_custom_fields[field_name]['multi']: for custom_field_option in custom_field_row['value']: custom_field_value += all_project_custom_field_options[int(custom_field_option)]['name'] custom_field_value += " ; " if (len(custom_field_value)) > 0: custom_field_value = custom_field_value[:-3] else: custom_field_value = all_project_custom_field_options[int(custom_field_row['value'])]['name'] return custom_field_value def get_requirement_parents(req_parents_ids,all_project_requirements): req_parents = "" for req_parent_id in req_parents_ids: req_parents += all_project_requirements[req_parent_id]['identifier'] + "\n" return req_parents def get_requirement_children(req_children_ids,all_project_requirements): req_children = "" for req_children_id in req_children_ids: req_children += all_project_requirements[req_children_id]['identifier'] + "\n" return req_children def filter_req_state(element): if element['state'] in DEFAULT_VALUES["states_to_consider"]: return True return False def get_map(api, endpoint: Str = "/", name: Str = "id", name_map_func: Callable[[Str], Boolean] = None, filter_func: Callable[[Str], Boolean]= None): """ Function that given an endpoint returns a dict with specific keys. If function is provided it generates the key. name_map_func must receive an object instance. Otherwise key will be the property of each object specified in name. """ mapping = {} if not name_map_func: name_map_func = lambda x: x[name] for inst in api.get(endpoint): if filter_func and not filter_func(inst): # Filtered out continue key = name_map_func(inst) if not mapping.get(key): mapping[key] = inst else: warnings.warn(f"Warning ---> Key: {key} already has an object. Some data may be lost in mapping.") return mapping def get_specification_requirements(all_project_requirements): spec_requirements = {} for requirement in all_project_requirements: if all_project_requirements[requirement]['specification'] == CURRENT_SPECIFICATION["id"]: spec_requirements[requirement] = all_project_requirements[requirement] return spec_requirements def helper_flag_line_for_deletion(value,Export_Remove_Empty_Fields): if value == "" and Export_Remove_Empty_Fields == "True": return "[TO_DELETE]" return value def create_file(api, file_name, file_data, project_id: int, obj=None): del api._session.headers['Content-Type'] result = api._session.request("POST", api._url + "files/", files={ 'file': (file_name, file_data), }, data={ 'project': project_id, 'folder': CURRENT_SPECIFICATION['specs_folder'], 'content_type': obj['contenttype'] if obj else 13, 'object_id': obj['id'] if obj else project_id, }) try: result.raise_for_status() new_file = result.json() except: new_file = None api._session.headers['Content-Type'] = "application/json" return new_file def create_specification_document(api, Export_Remove_Empty_Fields, document,all_project_requirements, specification_data, all_project_images, all_project_component_types, all_project_applicability_conditions,all_project_requirement_types,all_project_requirement_vms,all_project_requirement_ver_methods,all_deployment_user_groups, all_deployment_users, all_project_files, all_project_custom_fields, all_project_custom_field_options,all_project_custom_field_rows,all_project_components): print ("Going to create Specification document for -> "+ specification_data['name']) OUTPUT_FILE = Export_Document_Name+'.docx' CURRENT_SPECIFICATION["id"] = specification_data['id'] template_data = [] all_specification_requirements = get_specification_requirements(all_project_requirements) if len(all_specification_requirements) <1: print("No requirements for Specification -> "+ specification_data['name']) return CURRENT_SPECIFICATION["name"] = specification_data['name'] new_parser = HtmlToDocx() docx_list = {} req_url = {} #1st we will add requirements without section to the document no_section_requirements = get_requirements_without_section(all_specification_requirements) if len(no_section_requirements) >0: counter = 0 for requirement in no_section_requirements: counter += 1 reqdata = no_section_requirements[requirement] requirement_with_images = has_images(requirement, all_project_images) req_owner = get_requirement_owner(reqdata['owner'],all_deployment_users, all_deployment_user_groups) req_type = get_requirement_type(reqdata,all_project_requirement_types) req_children = get_requirement_children(reqdata['children'],all_project_requirements) req_parents = get_requirement_parents(reqdata['parents'],all_project_requirements) req_custom_column_one = get_requirement_custom_field(reqdata,all_project_custom_fields,all_project_custom_field_options,all_project_custom_field_rows,Export_Custom_Column_One) req_custom_column_two = get_requirement_custom_field(reqdata,all_project_custom_fields,all_project_custom_field_options,all_project_custom_field_rows,Export_Custom_Column_Two) req_vms = get_requirement_verification_methods_newline(reqdata['verification_methods']) req_vms_components = get_requirement_verification_methods_components_newline(reqdata['verification_methods'],all_project_components) req_vms_status = get_requirement_verification_methods_status_newline(reqdata['verification_methods']) req_tags = get_requirement_tags(reqdata['tags']) template_data.append({ "specification_name" : CURRENT_SPECIFICATION["name"] if counter == 1 else "", "section_name" : "", "req_id" : reqdata['identifier']+"_url" if Export_Create_Links == "True" else reqdata['identifier'], "req_title" : reqdata['title'], "req_text" : reqdata['identifier']+"_docx", "req_type" : helper_flag_line_for_deletion(req_type ,Export_Remove_Empty_Fields), "req_rationale" : helper_flag_line_for_deletion(reqdata['comment'] ,Export_Remove_Empty_Fields), "req_owner" : helper_flag_line_for_deletion(req_owner ,Export_Remove_Empty_Fields) , "req_children" : helper_flag_line_for_deletion(req_children ,Export_Remove_Empty_Fields), "req_parents" : helper_flag_line_for_deletion(req_parents ,Export_Remove_Empty_Fields) , "req_ver_methods" : helper_flag_line_for_deletion(req_vms ,Export_Remove_Empty_Fields), "req_ver_status" : helper_flag_line_for_deletion(req_vms_status ,Export_Remove_Empty_Fields), "req_ver_components" : helper_flag_line_for_deletion(req_vms_components ,Export_Remove_Empty_Fields), "req_tags" : helper_flag_line_for_deletion(req_tags ,Export_Remove_Empty_Fields), "images" : "Images_Placeholder_"+str(requirement) if requirement_with_images == True else "No_Images" }) docx_list[reqdata['identifier']+"_docx"] = new_parser.parse_html_string(reqdata['text']) if Export_Create_Links == "True" : req_url[reqdata['identifier']+"_url"]="https://"+DEFAULT_VALUES['domain']+"/project/"+str(Export_Project)+"/specifications/"+str(CURRENT_SPECIFICATION["id"])+"/requirements/"+str(reqdata['id']) no_loose_requirements = 0 else : no_loose_requirements = 1 #we need to get the specification section in the correct order to be added to the document specification_sections = get_specification_sections(api, Export_Project) #for each section we will get its ordered requirements and add to the document for section in specification_sections: section_requirements = get_section_requirements(all_specification_requirements,section) section_name = specification_sections[section]['name'] counter = 0 if len(section_requirements) == 0: template_data.append({ "specification_name" : CURRENT_SPECIFICATION["name"] if counter == 1 and no_loose_requirements == 1 else "", "section_name" : section_name, "req_id": "REMOVE_TABLE" }) for requirement in section_requirements: counter += 1 reqdata = section_requirements[requirement] requirement_with_images = has_images(requirement, all_project_images) req_owner = get_requirement_owner(reqdata['owner'],all_deployment_users, all_deployment_user_groups) req_type = get_requirement_type(reqdata,all_project_requirement_types) req_children = get_requirement_children(reqdata['children'],all_project_requirements) req_parents = get_requirement_parents(reqdata['parents'],all_project_requirements) req_custom_column_one = get_requirement_custom_field(reqdata,all_project_custom_fields,all_project_custom_field_options,all_project_custom_field_rows,Export_Custom_Column_One) req_custom_column_two = get_requirement_custom_field(reqdata,all_project_custom_fields,all_project_custom_field_options,all_project_custom_field_rows,Export_Custom_Column_Two) req_vms = get_requirement_verification_methods_newline(reqdata['verification_methods']) req_vms_components = get_requirement_verification_methods_components_newline(reqdata['verification_methods'],all_project_components) req_vms_status = get_requirement_verification_methods_status_newline(reqdata['verification_methods']) req_tags = get_requirement_tags(reqdata['tags']) template_data.append({ "specification_name" : CURRENT_SPECIFICATION["name"] if counter == 1 and no_loose_requirements == 1 else "", "section_name" : section_name if counter == 1 else "", "req_id" : reqdata['identifier']+"_url" if Export_Create_Links == "True" else reqdata['identifier'], "req_title" : reqdata['title'], "req_text" : reqdata['identifier']+"_docx", "req_type" : helper_flag_line_for_deletion(req_type ,Export_Remove_Empty_Fields), "req_rationale" : helper_flag_line_for_deletion(reqdata['comment'] ,Export_Remove_Empty_Fields), "req_owner" : helper_flag_line_for_deletion(req_owner ,Export_Remove_Empty_Fields) , "req_children" : helper_flag_line_for_deletion(req_children ,Export_Remove_Empty_Fields), "req_parents" : helper_flag_line_for_deletion(req_parents ,Export_Remove_Empty_Fields) , "req_ver_methods" : helper_flag_line_for_deletion(req_vms ,Export_Remove_Empty_Fields), "req_ver_status" : helper_flag_line_for_deletion(req_vms_status ,Export_Remove_Empty_Fields), "req_ver_components" : helper_flag_line_for_deletion(req_vms_components ,Export_Remove_Empty_Fields), "req_tags" : helper_flag_line_for_deletion(req_tags ,Export_Remove_Empty_Fields), "images" : "Images_Placeholder_"+str(requirement) if requirement_with_images == True else "No_Images" }) docx_list[reqdata['identifier']+"_docx"] = new_parser.parse_html_string(reqdata['text']) if Export_Create_Links == "True" : req_url[reqdata['identifier']+"_url"]="https://"+DEFAULT_VALUES['domain']+"/project/"+str(Export_Project)+"/specifications/"+str(CURRENT_SPECIFICATION["id"])+"/requirements/"+str(reqdata['id']) no_loose_requirements = 0 print ("Going to merge data into templates") document.merge_templates(template_data, separator='continuous_section') document.write(OUTPUT_FILE) document2 = Document(OUTPUT_FILE) remove_all_but_last_section(document2) print ("Going to put html formatting into document") put_html_text(document2, docx_list) adjust_headings(document2) if Export_Create_Links == "True": print ("Going to put Requirement Urls") put_url(document2, req_url) if Export_Create_Index == "True": create_reference_documents_index(document2,all_specification_requirements, all_project_files) print ("Going to put Images into document") put_images_and_remove_empty_fields(document2, all_project_images) keep_tables_on_one_page(document2) document2.save(OUTPUT_FILE) create_file(api, OUTPUT_FILE, open(OUTPUT_FILE,'rb'), Export_Project) print ("Specification document created -> "+ specification_data['name']) return def main(**kwargs): api = valispace.API(url=DEFAULT_VALUES["domain"],username=Username,password=Password,warn_https=False) all_project_requirement_states = get_map(api, f"vstates/?project="+str(Export_Project), "name") req_states_to_consider = [] for req_state in all_project_requirement_states: if all_project_requirement_states[req_state]["name"] in Export_Requirement_States: req_states_to_consider.append(all_project_requirement_states[req_state]['id']) DEFAULT_VALUES['states_to_consider'] = req_states_to_consider all_project_specifications = get_map(api, f"requirements/specifications/?project="+str(Export_Project), "name") all_project_images = get_map(api, f"files/?project="+str(Export_Project), "id", None, filter_images) all_project_component_types = get_map(api, f"components/types/", "id") all_project_components = get_map(api, f"components/?project="+str(Export_Project), "id") all_project_applicability_conditions = get_map(api, f"requirements/applicability-conditions/?project="+str(Export_Project), "id") all_project_requirement_types = get_map(api, f"requirements/types/?project="+str(Export_Project), "id") all_project_requirement_ver_methods = get_map(api, f"requirements/verification-methods/?project="+str(Export_Project), "id") all_project_requirement_vms = get_map(api, f"requirements/requirement-vms/?project="+str(Export_Project)+"&clean_html=text", "id") all_deployment_user_groups = get_map(api, f"group/", "id") all_deployment_users = get_map(api, f"user/", "id") all_project_files = get_map(api, f"files/?project="+str(Export_Project), "id") all_project_custom_fields = get_map(api, f"data/custom-fields/?project="+str(Export_Project), "name") all_project_custom_field_options = get_map(api, f"data/custom-field-options/?project="+str(Export_Project), "id") all_project_custom_field_rows = get_map(api, f"data/custom-field-row/?project="+str(Export_Project), "id") if Export_Requirement_States != "All": all_project_requirements = get_map(api, f"requirements/complete/?project="+str(Export_Project)+"&clean_html=text&clean_text=comment", "id", None,filter_req_state) else: all_project_requirements = get_map(api, f"requirements/complete/?project="+str(Export_Project)+"&clean_html=text&clean_text=comment", "id") import_file = api.get(f"files/{Export_Template_Id}/") readfile = BytesIO(urllib.request.urlopen(import_file['download_url'], context=ssl.SSLContext()).read()) specifications_folder = api.post('files/folders/', data={"project": Export_Project,"name": "ExportedSpecifications_"+datetime.datetime.now().strftime("%d-%b-%Y_%H:%M:%S")}) CURRENT_SPECIFICATION['specs_folder'] = specifications_folder['id'] if Export_Specification != "All": create_specification_document(api, Export_Remove_Empty_Fields,MailMerge(readfile, remove_empty_tables=False, auto_update_fields_on_open="auto"),all_project_requirements, all_project_specifications[Export_Specification], all_project_images, all_project_component_types,all_project_applicability_conditions,all_project_requirement_types,all_project_requirement_vms,all_project_requirement_ver_methods,all_deployment_user_groups, all_deployment_users, all_project_files, all_project_custom_fields, all_project_custom_field_options,all_project_custom_field_rows,all_project_components) else: for specification in all_project_specifications: create_specification_document(api,Export_Remove_Empty_Fields, MailMerge(readfile, remove_empty_tables=False, auto_update_fields_on_open="auto"),all_project_requirements, all_project_specifications[specification], all_project_images, all_project_component_types,all_project_applicability_conditions,all_project_requirement_types,all_project_requirement_vms,all_project_requirement_ver_methods,all_deployment_user_groups, all_deployment_users, all_project_files, all_project_custom_fields, all_project_custom_field_options,all_project_custom_field_rows,all_project_components) pass if __name__=='__main__': main()