Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
UCSDA/parser_v7.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
799 lines (688 sloc)
34.4 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Class parser_v7 - Carries over functionality from previous parser iteration | |
# with several differences: | |
# 1. Parser converted to class object | |
# 2. Parsing and HLR/Truth table components segregated | |
# 3. Additional robustness added to parser | |
# Conversion by Austin Deschenes | |
# Uses library document to create list of keywords/phrases. Takes separate document | |
# for analysis. Document is scanned for keywords in both text and tables - partial | |
# (up to MATCH_THRESHOLD) and exact matches are searched for. Matches are added to | |
# log which is printed to console. Additionally, a document identical to the one | |
# being analyzed is created with exact matches highlighted in green and partial | |
# matches highlighted in red | |
# Other stuff (astha): | |
# added hlr verification code | |
# new regex to handle different HLR formats | |
# cleaner xlsx formatting | |
(4/29) | |
# major bug fixes! | |
# can handle interpolating [<>=] conditions | |
# won't break on newline in HLR | |
# handles single conditions | |
# added parts lib handling!! | |
# Imports # | |
from __future__ import ( | |
absolute_import, division, print_function, unicode_literals | |
) | |
from docx import Document | |
from docx.document import Document as _Document | |
from docx.oxml.text.paragraph import CT_P | |
from docx.oxml.table import CT_Tbl | |
from docx.table import _Cell, Table | |
from docx.text.paragraph import Paragraph | |
from docx.shared import RGBColor | |
from difflib import SequenceMatcher | |
import re | |
import enchant | |
import os | |
import string | |
import shlex | |
from Tkinter import * | |
from openpyxl import load_workbook | |
from openpyxl.workbook import Workbook | |
from openpyxl.worksheet import Worksheet | |
from openpyxl.styles import Color, PatternFill, Font, Border | |
from openpyxl.styles import colors | |
from openpyxl.cell import Cell | |
# End of Imports # | |
MATCH_THRESHOLD = 0.9 | |
PARTIAL_MATCH = 'Partial Match' | |
EXACT_MATCH = 'Exact Match' | |
BAD_SPELL = 'Bad Spell' | |
NEWLINE = '\n' | |
COMP_OPS = {'>': '<=', '<': '>=', '<=': '>', '>=': '<', '=': '!=', '!=': '='} | |
BOOL_VALS = {'TRUE': '1', 'FALSE': '0'} | |
BOOL_OPS = {'TRUE': '0', 'FALSE': '1'} | |
ASCII_CHARS = list(string.digits) + list(string.letters) + [' ', '"', '_', '\t'] | |
# ADD NEW CATEGORIES HERE | |
PARTS_LIBRARY_TYPES = ['Boolean Hold', 'Timer'] | |
class Parser: | |
"""Provides functionality for SRS parsing, HLR grabbing/verification, and truth table creation""" | |
def __init__(self, gui, doc_path, rev_path, hlr_path): | |
# Set up class instance vars # | |
self.gui = gui | |
self.doc_path = doc_path | |
#self.doc_path = 'C:\\Users\\vitaFrui\\Downloads\\SRS-sample_v3.docx' | |
self.rev_path = rev_path + '\\' + doc_path.split('\\')[-1].split('.')[0] + '_revision.docx' | |
#self.rev_path = 'C:\\Users\\Public\\Desktop\\test.docx' | |
self.hlr_path = hlr_path | |
self.doc = Document(self.doc_path) | |
self.dict = enchant.Dict("en_US") | |
self.doc_text = None | |
self.doc_hlr = None | |
self.doc_hlr_parsed = None | |
# self.doc_library = [] | |
self.doc_library = None | |
self.hlr_keywords = ['any', 'all', 'otherwise', 'met', 'greater', 'less', 'equal', 'TRUE', 'True', 'true', 'FALSE', 'False', 'false'] | |
# dictionary of Parts Lib category : [keywords] | |
self.parts_lib_keywords = { | |
'Boolean Hold' : ['SET', 'RESET', 'OUT'], | |
'Timer' : ['OUT', 'SET', 'LATCH_TIME'], | |
} | |
def parse_doc(self): | |
""" | |
Parses the document given to the parser at creation time - builds variable library if one does not exist, | |
checks for variable matches in text and highlights them accordingly; also locates likely spelling errors | |
:return: | |
""" | |
if self.doc_library is None: | |
self.library_parse() | |
self.doc_text = '' | |
for block in self.iter_block_items(): | |
if isinstance(block, Paragraph): | |
self.doc_text = self.doc_text + block.text + '\n' | |
self.parse_paragraph(block) | |
elif isinstance(block, Table): | |
for row in block.rows: | |
for cell in row.cells: | |
for paragraph in cell.paragraphs: | |
self.doc_text = self.doc_text + paragraph.text + '\n' | |
self.parse_paragraph(paragraph) | |
self.doc.save(self.rev_path) | |
self.gui_print("SRS verified. Please revise and correct any marked errors:\n\n", '') | |
def parse_paragraph(self, my_paragraph): | |
""" | |
Parses an xml paragraph element from parent .docx file to highlight vars and spelling errors | |
:param my_paragraph: xml paragraph element to parse for variables and textual errors | |
:return: nothing | |
""" | |
my_text = my_paragraph.text | |
my_text_tokens = self.get_tokens(my_text) | |
my_paragraph.clear() | |
match_found = False | |
exact_match_found = False | |
good_spell = True | |
for token in my_text_tokens: | |
# check spelling (only considered if no match found) of normal words (just letter, not all caps) | |
if token.isupper() == False and bool(re.search(r'\d', token)) == False: | |
good_spell = self.dict.check(token) | |
for keyword in self.doc_library.keys(): | |
# if keyword == token: | |
if keyword.strip().lower() == token.strip().lower(): # handle whitespace and weird sizes | |
# handle exact match | |
self.log_match(EXACT_MATCH, token, keyword) | |
match_found = True | |
exact_match_found = True | |
elif SequenceMatcher(None, token, keyword).ratio() > MATCH_THRESHOLD: | |
# handle partial match | |
self.log_match(PARTIAL_MATCH, token, keyword) | |
match_found = True | |
if match_found or good_spell is False: | |
# split text on current keyword | |
# this code needs further development for more keyword/text cases | |
my_text_pieces = my_text.split(token, 1) | |
if len(my_text_pieces) > 1: | |
# keyword + text before/after | |
text_before_keyword = my_text_pieces[0] | |
my_text = my_text_pieces[1] | |
self.doc_add_run(my_paragraph, text_before_keyword, None) | |
else: | |
# only keyword or only keyword and text before OR after | |
my_text = my_text_pieces[0] | |
if exact_match_found: | |
self.doc_add_run(my_paragraph, token, EXACT_MATCH) | |
# gui_print("%s \n" % token, 'good') | |
elif match_found: | |
self.doc_add_run(my_paragraph, token, PARTIAL_MATCH) | |
#self.gui_print("%s \n" % token, 'bad_match') | |
else: | |
self.doc_add_run(my_paragraph, token, BAD_SPELL) | |
# gui_print("%s \n Try: " % token, 'error_spell') | |
# suggestions = DICT.suggest(token) | |
# for s in suggestions: | |
# gui_print("%s, " % s, '-') | |
# gui_print ("\n", '-') | |
# reset booleans for next iteration | |
match_found = False | |
exact_match_found = False | |
good_spell = True | |
# add any leftover text | |
self.doc_add_run(my_paragraph, my_text, None) | |
@staticmethod | |
def doc_add_run(my_paragraph, my_run_text, my_match_flag): | |
# used to add text to paragraph being parsed | |
if my_run_text == '': | |
# eliminate "empty" runs | |
return | |
if my_match_flag is PARTIAL_MATCH: | |
# handle partial match (COLOR = BLUE) | |
my_run = my_paragraph.add_run(my_run_text + ' ') | |
my_run_font = my_run.font | |
my_run_font.bold = True | |
my_run_font.color.rgb = RGBColor(0x00, 0x00, 0xFF) | |
elif my_match_flag is EXACT_MATCH: | |
# handle exact match (COLOR = GREEN) | |
my_run = my_paragraph.add_run(my_run_text + ' ') | |
my_run_font = my_run.font | |
my_run_font.bold = True | |
my_run_font.color.rgb = RGBColor(0x32, 0xCD, 0x32) | |
elif my_match_flag is BAD_SPELL: | |
# handle bad spelling (COLOR = PURPLE) | |
my_run = my_paragraph.add_run(my_run_text + ' ') | |
my_run_font = my_run.font | |
my_run_font.bold = True | |
my_run_font.color.rgb = RGBColor(0x66, 0x00, 0xFF) | |
else: | |
# add runs w/o matches | |
my_paragraph.add_run(my_run_text) | |
@staticmethod | |
def get_tokens(my_text): | |
# remove non-alphanumeric chars, except underscore, and tokenize | |
pattern = re.compile('[\W]+', re.UNICODE) | |
tokens_ascii = shlex.split(filter(lambda x: x in ASCII_CHARS, my_text)) | |
tokens = [pattern.sub(" ", elem) for elem in tokens_ascii] | |
return tokens | |
@staticmethod | |
def log_match(my_match_type, my_matching_token, my_matching_keyword): | |
# simple method to log matches | |
my_log = '' | |
my_log += my_match_type + ': ' + \ | |
my_matching_token + ' / ' + my_matching_keyword + NEWLINE | |
return my_log | |
def library_parse(self): | |
# f = open('library1.txt', 'w') | |
self.doc_library = {} | |
for table in self.doc.tables: | |
# selects each table from the document which has "Signal Name" in the leftmost | |
if table.cell(0, 0).text == 'Signal Name': | |
# remove the header cell | |
s = (table.column_cells(0)) | |
s.pop(0) | |
v = (table.column_cells(1)) | |
v.pop(0) | |
for cell,cell2 in zip(s, v): | |
var_name = cell.text.strip() | |
var_type = 0 | |
if 'bool' in cell2.text.lower() : | |
var_type = 1 | |
vt = self.doc_library.get(var_name, -1) | |
# check to make sure var was not already identified | |
if vt != -1: | |
self.gui_print("Variable {} defined more than once.\n".format(var_name), '') | |
if vt != var_type: | |
self.gui_print("New definition for {} contradicts an old one.\n".format(var_name), '') | |
self.doc_library[var_name] = var_type | |
for k in self.doc_library.keys(): | |
print ("%s --> %d" % (k, self.doc_library[k])) | |
def get_all_hlr(self): | |
# attempts to parse out all HLR related text from doc based on pattern matching | |
if self.doc_text is None: | |
self.parse_doc() | |
my_text_lines = self.doc_text.split('\n') | |
# HLR start pattern | |
hlr_match_start = [] | |
hlr_match_end = [] | |
in_hlr = False | |
hlr_start_pattern = re.compile('^The[^.]*shall (?!provide)', re.IGNORECASE) | |
hlr_cont_pattern = re.compile(r"<<[\w]+>>|func|\b%s\b" % r"\b|\b".join(self.hlr_keywords), re.IGNORECASE) | |
for i in range(0, len(my_text_lines)): | |
if not in_hlr and hlr_start_pattern.search(my_text_lines[i]): | |
hlr_match_start.append(i - 1) | |
in_hlr = True | |
continue | |
if in_hlr: | |
if (my_text_lines[i].strip() == ''): | |
continue | |
if hlr_start_pattern.search(my_text_lines[i]): | |
hlr_match_start.append(i) | |
hlr_match_end.append(i) | |
in_hlr = False | |
elif not hlr_cont_pattern.search(my_text_lines[i]): | |
hlr_match_end.append(i) | |
in_hlr = False | |
self.doc_hlr = [] | |
for i in range(0, len(hlr_match_start)): | |
start = hlr_match_start[i] | |
end = hlr_match_end[i] | |
print (my_text_lines[start] + " : " + my_text_lines[end]) | |
self.doc_hlr.append("\n".join(my_text_lines[start:end])) | |
return self.doc_hlr | |
def parse_all_hlr(self): | |
# get info from HLR - (tries) to get rid of all non functional HLRs | |
if self.doc_hlr is None: | |
self.get_all_hlr() | |
# print (self.doc_hlr) | |
# for h in self.doc_hlr: | |
# print ("\nbf: ", h[0:18], h[-28:]) | |
all_func_hlr = [hlr for hlr in self.doc_hlr if 'FUNC' in hlr.upper()] | |
# for h in all_func_hlr: | |
# print ("\nfunc: ", h[0:18]) | |
self.doc_hlr_parsed = [self.parse_single_hlr(func_hlr) for func_hlr in all_func_hlr] | |
return self.doc_hlr_parsed | |
def parse_single_hlr(self, my_hlr): | |
# hlr parsing tool that can be called to grab info | |
# built pattern and use it to grab all keywords | |
# Ex format: For HLR Object 1_1 in Table of HLRs the following is provided: | |
# [['<<CC>>', 'TRUE'], ['and', ['<<A>>', 'TRUE'], ['<<B>>', 'TRUE']]] | |
uses_PL = 0 # parts library indicator | |
# keywords, expand/add as needed - but must update _parse_single_hlr_helper as well | |
hlr_pattern = re.compile(r"<<[\w]+>>|\b%s\b" % r"\b|\b".join(self.hlr_keywords)) | |
# HERE: deal with variations of parts libraries | |
## HERE IS WHERE YOU HANDLE PARTS LIB HLRS DIFFERENTLY | |
if 'parts lib' in my_hlr.lower(): | |
uses_PL = 1 | |
parts_lib_type = '' | |
for plt in PARTS_LIBRARY_TYPES: | |
if plt.lower() in my_hlr.lower(): | |
parts_lib_type = plt # mark the parts library type | |
# print ("$$$$$" + my_hlr + '---------------' + parts_lib_type) | |
hlr_pattern = re.compile(r"<<[\w]+>>|\b%s\b" % r"\b|\b".join( self.parts_lib_keywords[parts_lib_type] )) | |
hlr_tokens = re.findall(hlr_pattern, my_hlr) | |
# print ("=====================\n" + my_hlr[1:8] + "\n MATCHES \n" + ", ".join(hlr_tokens) + "\n") | |
parsed_hlr = hlr_tokens | |
# DEBUGGING HERE | |
# print "Tokens:" | |
# print hlr_tokens | |
# print "Length = " + str(len(hlr_tokens)) | |
# exit() | |
if not uses_PL: | |
parsed_hlr = [hlr_tokens[0], hlr_tokens[1]] | |
next_part = self.hlr_token_handler(hlr_tokens[2:]) | |
if len(next_part) == 1: | |
next_part = next_part[0] | |
parsed_hlr = [parsed_hlr, next_part] | |
print (parsed_hlr) | |
return [my_hlr.splitlines()[0].strip(), parsed_hlr, uses_PL] | |
# def parse_parts_lib_hlr(self, pl_hlr): | |
# parts_lib_type = '' | |
# for plt in PARTS_LIBRARY_TYPES: | |
# if plt.lower() in pl_hlr.lower(): | |
# parts_lib_type = plt # mark the parts library type | |
# ## define the parts library formats here | |
# BoolHold_keywords = ['SET', 'RESET', 'OUT'] | |
# Timer_keywords = ['OUT', 'SET', 'LATCH_TIME'] | |
# if parts_lib_type == 'Boolean Hold': | |
# hlr_pattern = re.compile(r"<<[\w]+>>|\b%s\b" % r"\b|\b".join(BoolHold_keywords)) | |
# hlr_tokens = re.findall(hlr_pattern, pl_hlr) | |
# print ("=====================\n" + pl_hlr + "\n ~~MATCHES~~ \n" + ", ".join(hlr_tokens) + "\n") | |
def hlr_token_handler(self, my_hlr_tokens, index=0, met_flag=-1): | |
if index < len(my_hlr_tokens): | |
hlr_token = my_hlr_tokens[index] | |
else: | |
return [] | |
parsed_hlr = [] | |
if hlr_token == 'any': | |
parsed_hlr = ['or'] | |
next_hlr_token = my_hlr_tokens[index + 1] | |
if next_hlr_token == 'TRUE' or next_hlr_token == 'FALSE': | |
parsed_hlr.extend(self.hlr_token_handler(my_hlr_tokens, index + 2, (0 if next_hlr_token == 'FALSE' else 1))) | |
else: | |
parsed_hlr.extend(self.hlr_token_handler(my_hlr_tokens, index + 1, -1)) | |
elif hlr_token == 'all': | |
parsed_hlr = ['and'] | |
next_hlr_token = my_hlr_tokens[index + 1] | |
if next_hlr_token == 'TRUE' or next_hlr_token == 'FALSE': | |
parsed_hlr.extend(self.hlr_token_handler(my_hlr_tokens, index + 2, (0 if next_hlr_token == 'FALSE' else 1))) | |
else: | |
parsed_hlr.extend(self.hlr_token_handler(my_hlr_tokens, index + 1, -1)) | |
elif hlr_token == 'greater': | |
# parsed_hlr = ['>', my_hlr_tokens[index - 1], my_hlr_tokens[index + 1]] | |
parsed_hlr.append(['>', my_hlr_tokens[index - 1], my_hlr_tokens[index + 1]]) | |
parsed_hlr.extend(self.hlr_token_handler(my_hlr_tokens, index + 2, met_flag)) | |
elif hlr_token == 'less': | |
# parsed_hlr = ['<', my_hlr_tokens[index - 1], my_hlr_tokens[index + 1]] | |
parsed_hlr.append(['<', my_hlr_tokens[index - 1], my_hlr_tokens[index + 1]]) | |
parsed_hlr.extend(self.hlr_token_handler(my_hlr_tokens, index + 2, met_flag)) | |
elif hlr_token == 'equal': | |
# parsed_hlr = ['=', my_hlr_tokens[index - 1], my_hlr_tokens[index + 1]] | |
parsed_hlr.append(['=', my_hlr_tokens[index - 1], my_hlr_tokens[index + 1]]) | |
parsed_hlr.extend(self.hlr_token_handler(my_hlr_tokens, index + 2, met_flag)) | |
elif hlr_token == 'TRUE' and met_flag == -1 or hlr_token == 'FALSE' and met_flag == -1: | |
parsed_hlr.append([my_hlr_tokens[index - 1], hlr_token]) | |
parsed_hlr.extend(self.hlr_token_handler(my_hlr_tokens, index + 1, met_flag)) | |
elif "<<" in hlr_token and met_flag != -1: | |
parsed_hlr.append([my_hlr_tokens[index], 'FALSE' if met_flag == 0 else 'TRUE']) | |
parsed_hlr.extend(self.hlr_token_handler(my_hlr_tokens, index + 1, met_flag)) | |
else: | |
parsed_hlr = self.hlr_token_handler(my_hlr_tokens, index + 1) | |
return parsed_hlr | |
def verify_all_hlr(self): | |
# Double checks all HLRs in document to make sure they follow HLR rules | |
# DOES NOT VERIFY PARTS LIBRARY HLRs - we don't have clear enough info on that yet | |
if self.doc_hlr is None: | |
self.get_all_hlr() | |
titles = set() | |
for hlr in self.doc_hlr: | |
# print ("------------------------------\n", hlr, "------------------------------\n") | |
if hlr == '': | |
continue | |
lines = hlr.splitlines() | |
title = lines[0].strip() | |
# verify title only used once | |
if title in titles: | |
self.gui_print("HLR title %s is used more than once.\n" % title, '') | |
titles.add(title) | |
# verify 'shall' only used once | |
if hlr.lower().split().count('shall') != 1: | |
self.gui_print("%s contains # of 'shall'.\n" % title, '') | |
self.doc_hlr.remove(hlr) | |
continue | |
# prelim detection of parts lib hlr | |
if 'parts lib' in hlr.lower(): | |
parts_lib_type = '' | |
for plt in PARTS_LIBRARY_TYPES: | |
if plt.lower() in hlr.lower(): | |
parts_lib_type = plt # mark the parts library type | |
if parts_lib_type == '': | |
self.gui_print("Skipping %s, uses unknown Parts Lib.\n" % title, '') | |
self.doc_hlr.remove(hlr) | |
else: | |
self.gui_print("%s uses %s Parts Lib.\n" % (title, parts_lib_type), '') | |
# self.doc_hlr.remove(hlr) | |
continue | |
# look through parse array for inconsistencies | |
if self.doc_hlr_parsed is None: | |
self.parse_all_hlr() | |
parsed_list = self.doc_hlr_parsed | |
var_pattern = re.compile('^<<[^ ]*>>') | |
arr_index = 0 | |
while arr_index < len(parsed_list): | |
arr = parsed_list[arr_index] | |
# for a in ['', '1', '1']: | |
# arr = [ hlr name, [ ['<<CC>>', 'TRUE'], ['and', ['<<A>>', 'TRUE'], ['<', '<<B>>', '<<X1>>'] ] ], parts lib indicaton ] | |
print ("verif: ", arr) | |
title = arr[0] # hlr name | |
main_hlr = arr[1] # [ ['<<CC>>', 'TRUE'], ['and', ['<<A>>', 'TRUE'], ['<', '<<B>>', '<<X1>>'] ] ] | |
if len(main_hlr) < 2: # invalid HLR array | |
parsed_list.remove(arr) | |
continue | |
if arr[2] == 1: # skip any parts library hlrs | |
arr_index += 1 | |
continue | |
test_case = main_hlr[0] # ['<<CC>>', 'TRUE'] | |
conditions = main_hlr[1] # ['and', ['<<A>>', 'TRUE'], ['<', '<<B>>', '<<X1>>'] ] | |
print (" arr inde: ", arr_index) | |
print (" title: ", title) | |
print (" main_hlr: ", main_hlr) | |
print (" test_cas: ", test_case) | |
print (" conditio: ", conditions, "len : ", len(conditions)) | |
# continue | |
# check for test case to contain variable | |
if not var_pattern.match(test_case[0]): | |
self.gui_print("%s may be missing a <<variable>> declaration.\n" % title, '') | |
parsed_list.remove(arr) | |
continue | |
elif not self.doc_library.has_key(test_case[0][2:-2]): | |
self.gui_print("Variable {} in {} is not declared in library.\n".format(test_case[0], title ), '') | |
elif (self.doc_library[test_case[0][2:-2]] == 1 and test_case[1] not in BOOL_VALS.keys()) or (self.doc_library[test_case[0][2:-2]] == 0 and test_case[1] in BOOL_VALS.keys()): # if a boolean var is not working with boolean val... | |
self.gui_print("Variable %s in %s is of inconsistent type.\n" % (test_case[0], title), '') | |
elif len(conditions) >= 3 and str(conditions[0]) != 'and' and str(conditions[0]) != 'or' and str(conditions[0]) not in COMP_OPS.keys() : | |
self.gui_print("%s has poor syntax and will be skipped.\n" % title, '') | |
parsed_list.remove(arr) | |
continue | |
elif conditions[0] == 'and' or conditions[0] == 'or': | |
# conditions.pop(0) | |
for c_sub in conditions[1:]: | |
if c_sub[0] in COMP_OPS.keys(): # clip the '>' signs from the front | |
c_sub = c_sub[1:] | |
for sub_var in c_sub: | |
if sub_var != 'TRUE' and sub_var != 'FALSE' and not var_pattern.match(sub_var): | |
# now would be the time to deal with data variables | |
# if sub_var is data variable: | |
# continue | |
# else: | |
self.gui_print("%s may be missing a <<variable>> declaration.\n" % title, '') | |
parsed_list.remove(arr) | |
continue | |
if not self.doc_library.has_key(c_sub[0][2:-2]): | |
self.gui_print("Variable {} in {} is not declared in library.\n".format(c_sub[0], title ), '') | |
elif (self.doc_library[c_sub[0][2:-2]] == 1 and c_sub[1] not in BOOL_VALS.keys()) or (self.doc_library[c_sub[0][2:-2]] == 0 and c_sub[1] in BOOL_VALS.keys()): # if a boolean var is not working with boolean val... | |
self.gui_print("Variable %s in %s is of inconsistent type.\n" % (c_sub[0], title), '') | |
elif conditions[0] in COMP_OPS.keys(): | |
# conditions[0].pop(0) | |
for sub_var in conditions[0][1:]: | |
if not var_pattern.match(sub_var): | |
# now would be the time to deal with data variables | |
# if sub_var is data variable: | |
# continue | |
# else: | |
self.gui_print("%s may be missing a <<variable>> declaration.\n" % title, '') | |
parsed_list.remove(arr) | |
continue | |
elif var_pattern.match(str(conditions[0])): # make sure 1st var is a variable | |
if not self.doc_library.has_key(str(conditions[0])[2:-2]): | |
self.gui_print("Variable {} in {} is not declared in library.\n".format(str(conditions[0]), title ), '') | |
elif (self.doc_library[str(conditions[0])[2:-2]] == 1 and str(conditions[1]) not in BOOL_VALS.keys()) or (self.doc_library[str(conditions[0])[2:-2]] == 0 and str(conditions[1]) in BOOL_VALS.keys()): # if a boolean var is not working with boolean val... | |
self.gui_print("Variable %s in %s is of inconsistent type.\n" % (str(conditions[0]), title), '') | |
else: | |
self.gui_print("%s has bad syntax.\n" % title, '') | |
parsed_list.remove(arr) | |
continue | |
arr_index+=1 | |
self.doc_hlr_parsed = parsed_list | |
def make_all_truth_tables(self): | |
if self.doc_hlr_parsed is None: | |
self.parse_all_hlr() | |
for ph in self.doc_hlr_parsed: | |
self.make_truth_table(ph) | |
def make_truth_table(self, given_hlr): | |
# [ hlr name, [['<<CC>>', 'TRUE'], ['and', ['<<A>>', 'TRUE'], ['<<B>>', 'TRUE']]] , uses parts library ] | |
# [['<<CC>>', 'TRUE'], ['>', '<<A>>', '<<NV>>']] | |
hlr_title = given_hlr[0] ## name | |
p_hlr = given_hlr[1] ## [whole thing] | |
uses_PL = given_hlr[2] ## parts lib indicator | |
if uses_PL == 1: | |
## HANDLE PULLING PARTS LIB TEMPLATES (maybe use .csv or .xlsx?) | |
return [-1] | |
test_var = p_hlr[0] ## [<<c>> t] | |
test_name = test_var[0] ## [] | |
test_res = '0->1' | |
test_res_op = '1->0' | |
indexes_to_switch_res = [] # array of result matrix indices where result must be set to opposite (<>= operators) | |
# hlr_column_names = ['Sub test case / ID Variables'] # array of col names for each variable comparison | |
if test_var[1] == 'FALSE': # if CC is set to a boolean, flip resulting strings | |
trt = test_res | |
test_res = test_res_op | |
test_res_op = test_res | |
elif test_var[1] != 'TRUE': # if CC is set to some value, edit the name to become the value | |
test_name = test_var[0] + ' = ' + test_var[1] | |
conditions = p_hlr[1] | |
res_arr = [] | |
if len(conditions) < 1: # [] WEIRD CASE | |
return [-1] | |
gate = conditions[0] | |
if gate == 'or' or gate == 'and': | |
num_conds = len(conditions)-1 | |
# print ("# conditions", num_conds, "conditions: ", conditions) | |
# now handle condition options | |
i = 1 | |
while i <= len(conditions)-1: | |
# print ("i: ", i) | |
cond_type = conditions[i][0] | |
if len(conditions[i]) < 2: # ['or'] ['TRUE'] WEIRD CASES | |
return [-1] | |
if cond_type == '>' or cond_type == '<' or cond_type == '=': | |
# first, add new default column for old rows (since these ops require an extra 'condition' row) | |
ri = 0 | |
while ri < len(res_arr): | |
if 'becomes' in res_arr[ri][0]: | |
if gate == 'and': | |
res_arr[ri].append('1') | |
res_arr[ri+1].append('0') | |
else: | |
res_arr[ri].append('0') | |
res_arr[ri+1].append('1') | |
ri += 1 | |
else: | |
char = res_arr[ri][1] | |
if gate == 'and': | |
res_arr[ri].append(char[-1]) | |
else: | |
res_arr[ri].append(char[0]) | |
ri += 1 | |
# now create the two rows for the conditions | |
var = conditions[i][1] | |
comp_var = conditions[i][2] | |
c1 = var + " becomes " + cond_type + " " + comp_var | |
c2 = var + " becomes " + COMP_OPS[cond_type] + " " + comp_var | |
crow1 = [c1] | |
crow2 = [c2] | |
j = 1 | |
while j < num_conds + 2: | |
if j == i: # changing tested condition | |
crow1.append('0->1') | |
crow2.append('0') | |
elif j == i + 1: # changing opposite condition | |
crow1.append('0') | |
crow2.append('0->1') | |
else: | |
if gate == 'and': | |
crow1.append('1') | |
crow2.append('0') # basic 'and' condition | |
else: | |
crow1.append('0') | |
crow2.append('1') # basic 'or' condition | |
j += 1 | |
res_arr.append(crow1) | |
res_arr.append(crow2) | |
indexes_to_switch_res.append(i+1) # record the switch of opposite conditions | |
i += 1 | |
num_conds += 1 | |
else: | |
cond_bool = conditions[i][1] | |
crow = [] | |
crow.append(cond_type) # first elem is variable | |
j = 1 | |
while j < num_conds+1: | |
if j == i: # changing condition | |
crow.append( BOOL_OPS[cond_bool] + '->' + BOOL_VALS[cond_bool] ) | |
else: | |
if gate == 'and': | |
crow.append('' + BOOL_VALS[cond_bool]) # basic 'and' condition | |
else: | |
crow.append('' + BOOL_OPS[cond_bool]) # basic 'or' condition | |
j += 1 | |
res_arr.append(crow) | |
i += 1 | |
elif conditions[0] == '>' or conditions[0] == '<' or conditions[0] == '=': | |
var = conditions[1] | |
comp_var = conditions[2] | |
c1 = var + " becomes " + conditions[0] + " " + comp_var | |
c2 = var + " becomes " + COMP_OPS[conditions[0]] + " " + comp_var | |
crow1 = [c1, '0->1', '0'] | |
crow2 = [c2, '0', '0->1'] | |
res_arr.append(crow1) | |
res_arr.append(crow2) | |
indexes_to_switch_res.append(2) | |
elif str(conditions[0])[0:2] == '<<': | |
conditions = ['and', conditions] | |
return self.make_truth_table( [hlr_title, [test_var,conditions], uses_PL] ) | |
else: | |
return [-1] | |
test_arr = [] | |
test_arr.append(test_name) # first elem is variable | |
j = 1 | |
if len(res_arr) < 1: | |
return [-1] | |
while j < len(res_arr[0]): # handle every conditional column | |
if j in indexes_to_switch_res: # changing condition | |
test_arr.append( test_res_op ) | |
else: | |
test_arr.append( test_res ) | |
j += 1 | |
res_arr.append(test_arr) | |
self.gui_print(" - " + hlr_title + "\n", '') | |
# for RR in res_arr: | |
# print (RR) | |
# print ("\n\n") | |
return res_arr | |
def make_test_cases(self): | |
if self.doc_hlr_parsed is None: | |
self.verify_all_hlr() | |
wb = Workbook() | |
ws = wb.active | |
grayFill = PatternFill(start_color='D9D9D9', end_color='D9D9D9', fill_type='solid') | |
if os.path.isfile(self.hlr_path) and self.hlr_path.split('\\')[-1].split('.')[1] == 'xlsx': | |
wb = load_workbook(self.hlr_path) | |
ws = wb.create_sheet() | |
ws.title = "Truth Tables" | |
ws_row = 2 | |
ws_col = 'C' | |
self.gui_print("\nThe following HLRs were turned into test cases:\n", '') | |
for arr in self.doc_hlr_parsed: | |
print (arr) | |
print ("\n ------------ \n") | |
test_case = self.make_truth_table(arr) | |
# skip empty results | |
if test_case == [-1]: | |
continue | |
tc_size = len(test_case[0]) | |
# print title of TT and any PL reqs | |
ws[ws_col + str(ws_row) ] = arr[0] | |
ws_row += 1 | |
ws[ws_col + str(ws_row)] = 'PL requirements?' | |
ws_row += 2 | |
# print corner cell | |
ws.cell(ws_col + str(ws_row)).style.alignment.wrap_text = True | |
ws[ws_col + str(ws_row)] = "Sub test case / ID Variables" | |
ws[ws_col + str(ws_row)].fill = grayFill | |
ws[ws_col + str(ws_row)].font = Font(bold=True) | |
# add column names | |
for i in range(1, tc_size): | |
col_let = chr(ord(ws_col) + i) | |
ws[col_let + str(ws_row)] = "TCX_" + chr(ord(col_let) + 10) | |
ws[col_let + str(ws_row)].fill = grayFill | |
ws[col_let + str(ws_row)].font = Font(bold=True) | |
ws_row += 1 | |
# make actual test case | |
for tc_row in test_case: | |
i = 0 | |
for tc_element in tc_row: | |
ws[chr(ord(ws_col) + i) + str(ws_row)] = tc_element | |
if i == 0: | |
ws[chr(ord(ws_col) + i) + str(ws_row)].fill = grayFill | |
ws[chr(ord(ws_col) + i) + str(ws_row)].font = Font(bold=True) | |
i += 1 | |
ws_row += 1 | |
ws_row += 2 | |
print ("\n _______________________________________________ \n") | |
# _make_all_truth_tables(parsed_arrays) | |
ws.column_dimensions[ws_col].width = 28 | |
wb.save(self.hlr_path.split('.')[0] + '.xlsx') | |
def iter_block_items(self): | |
""" | |
Generate a reference to each paragraph and table child within *parent*, | |
in document order. Each returned value is an instance of either Table or | |
Paragraph. *parent* would most commonly be a reference to a main | |
Document object, but also works for a _Cell object, which itself can | |
contain paragraphs and tables. | |
""" | |
if isinstance(self.doc, _Document): | |
parent_elm = self.doc.element.body | |
# print(parent_elm.xml) | |
elif isinstance(self.doc, _Cell): | |
parent_elm = self.doc._tc | |
else: | |
raise ValueError("something's not right") | |
for child in parent_elm.iterchildren(): | |
if isinstance(child, CT_P): | |
yield Paragraph(child, self.doc) | |
elif isinstance(child, CT_Tbl): | |
yield Table(child, self.doc) | |
def gui_print(self, text, code): | |
self.gui.insert('end', text, code) | |
# define colors of tags 'error' and 'good' and any others | |
self.gui.tag_configure('error_spell', foreground='red') | |
self.gui.tag_configure('good', foreground='green') | |
self.gui.tag_configure('bad_match', foreground='blue') | |
self.gui.tag_configure('', foreground='black') | |