# Copyright 2024-2026 Universidad de Sevilla
# SPDX-License-Identifier: Apache-2.0
"""
Parser for formal signoff reports.
This module provides functions to parse the coverage tables and convert them
into a unified coverage table format.
It is specifically for Questa PropCheck results.
"""
import re
[docs]
def parse_coverage_table(html):
"""
Parse formal signoff tables from HTML content.
:param html: HTML string containing coverage tables.
:type html: str
:return: A list of parsed tables with titles and row data.
:rtype: list[dict[str, list[dict[str, str]]]]
"""
tables = []
button_pattern = re.compile(r"<button.*?>(.*?)</button>", re.DOTALL)
table_pattern = re.compile(r"<table>.*?</table>", re.DOTALL)
row_pattern = re.compile(r"<tr.*?>(.*?)</tr>", re.DOTALL)
cell_pattern = re.compile(r"<t[dh].*?>(.*?)</t[dh]>", re.DOTALL)
buttons = button_pattern.findall(html)
tables_html = table_pattern.findall(html)
for title, table_html in zip(buttons, tables_html):
rows = row_pattern.findall(table_html)
headers = [re.sub(r"<.*?>", "", cell).strip() for cell in cell_pattern.findall(rows[0])]
data = []
for row in rows[1:]:
cells = [re.sub(r"<.*?>", "", cell).strip() for cell in cell_pattern.findall(row)]
data.append({headers[i]: cells[i] for i in range(len(cells))})
tables.append({
'title': title.strip(),
'data': data
})
return tables
[docs]
def filter_coverage_tables(tables):
"""
Filter coverage tables to select only the design summary,
not the individual module summaries.
:param tables: List of coverage tables with titles and data.
:type tables: list[dict]
:return: Filtered list of coverage tables.
:rtype: list[dict]
"""
filtered = [t for t in tables if t['title'].startswith('Formal Coverage Summary for Design')]
return filtered if filtered else [tables[0]] if tables else []
[docs]
def add_total_field(table):
"""
Add a total row to the coverage table
:param table: A coverage table with parsed row data.
:type table: dict
:return: The input table with an additional total row.
:rtype: dict
"""
total_row = {key: 0 for key in table['data'][0].keys()
if key not in ['Coverage Type', 'Covered (P)']}
total_covered = 0
total_possible = 0
for row in table['data']:
for key in total_row:
match = re.search(r'\d+', row[key])
total_row[key] += int(match.group()) if match else 0
# Extraer valores de Covered (P)
covered_match = re.search(r'(\d+)', row['Covered (P)'])
if covered_match:
total_covered += int(covered_match.group())
# Calcular posibles casos (Total - Excluded)
total_value = int(row.get('Total', 0))
excluded_value = int(row.get('Excluded', 0)) if 'Excluded' in row else 0
total_possible += total_value - excluded_value
# Calcular el porcentaje total
coverage_percentage = (total_covered / total_possible * 100) if total_possible > 0 else 0
total_row['Coverage Type'] = 'Total'
total_row['Covered (P)'] = f"{total_covered} ({coverage_percentage:.1f}%)"
table['data'].append(total_row)
return table