Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write table #22

Merged
merged 4 commits into from
Dec 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions pyLSV2/tab_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""module with reader for TNC tables"""
import logging
import re
import json
from pathlib import Path


class ColumnDescription():
def __init__(self, column_name, format_str):
self._name = column_name
self._format = format_str

class TabelFormat():
def __init__(self):
self.column_definitions = list()

class GenericTabel():
@staticmethod
def from_json(json_path):
tf = TabelFormat()
with open(json_path, 'r') as jfp:
data = json.load(jfp)

for col in data['columns']:
print(col)
cd = ColumnDescription(column_name=col['column_name'], format_str=col['format_str'])
tf.column_definitions.append(cd)

if __name__ == "__main__":
file = Path('./tool_table.json')
GenericTabel.from_json(file)
274 changes: 230 additions & 44 deletions pyLSV2/table_reader.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""module with reader for TNC tables"""
"""module with reader and writer for TNC tables"""
import json
import logging
import re
import json
from pathlib import Path


Expand All @@ -14,62 +16,53 @@ class TableReader():
def __init__(self):
"""init object variables logging"""
logging.getLogger(__name__).addHandler(logging.NullHandler())
self.name = None
self.suffix = None
self.version = None
self.has_unit = False
self.is_metric = False

def parse_table(self, table_path):
"""Parse a file of one of the common table formats

:param str or Path table_path: Path to the table file

:returns: list od dictionaries. key is the column name, value the content of the table cell
:rtype: list
:rtype: NCTabel
"""
self.name = None
self.suffix = None
self.version = None
self.has_unit = False
self.is_metric = False
nctable = NCTabel()

table_file = Path(table_path)
if not table_file.is_file():
raise FileNotFoundError('Could not open file %s' % table_path)
raise FileNotFoundError(f"Could not open file {table_path:s}")

with table_file.open(mode='r') as tfp:
with table_file.open(mode='r', encoding='utf-8') as tfp:
header_line = tfp.readline().strip()
logging.debug('Checking line for header: %s', header_line)
header = re.match(
r"^BEGIN (?P<name>[a-zA-Z_ 0-9]*)\.(?P<suffix>[A-Za-z0-9]{1,4})(?P<unit> MM| INCH)?(?: Version: \'Update:(?P<version>\d+\.\d+)\')?$",
r"^BEGIN (?P<name>[a-zA-Z_ 0-9]*)\.(?P<suffix>[A-Za-z0-9]{1,4})(?P<unit> MM| INCH)?(?: (Version|VERSION): \'Update:(?P<version>\d+\.\d+)\')?$",
header_line)

if header is None:
raise Exception(
'File has wrong format: incorrect header for file %s' % table_path)
f'File has wrong format: incorrect header for file {table_path}')

self.name = header.group('name').strip()
self.suffix = header.group('suffix')
self.version = header.group('version')
nctable.name = header.group('name').strip()
nctable.suffix = header.group('suffix')
nctable.version = header.group('version')

if header.group('unit') is not None:
self.has_unit = True
nctable.has_unit = True
if 'MM' in header.group('unit'):
self.is_metric = True
nctable.is_metric = True
logging.debug(
'Header Information for file "%s" Name "%s", file is metric, Version: "%s"',
table_file, self.name, self.version)
table_file, nctable.name, nctable.version)
else:
self.is_metric = False
nctable.is_metric = False
logging.debug(
'Header Information for file "%s" Name "%s", file is inch, Version: "%s"',
table_file, self.name, self.version)
table_file, nctable.name, nctable.version)
else:
self.has_unit = False
self.is_metric = False
nctable.has_unit = False
nctable.is_metric = False
logging.debug('Header Information for file "%s" Name "%s", file has no units, Version: "%s"',
table_file, self.name, self.version)
table_file, nctable.name, nctable.version)

next_line = tfp.readline()
if '#STRUCTBEGIN' in next_line or 'TableDescription' in next_line:
Expand All @@ -82,31 +75,25 @@ def parse_table(self, table_path):
next_line = tfp.readline()
next_line = tfp.readline()

column_header = next_line
column_list = list()
column_pattern = re.compile(r"([A-Za-z-12_:\.]+)(?:\s+)")
for column_match in column_pattern.finditer(column_header):
column_list.append({'start': column_match.start(),
'end': column_match.end()-1,
'name': column_match.group().strip()})
for column_match in column_pattern.finditer(next_line):
nctable.append_column(name=column_match.group().strip(
), start=column_match.start(), end=column_match.end())

logging.debug('Found %d columns', len(column_list))

table_content = list()
logging.debug('Found %d columns', len(nctable.get_column_names()))

for line in tfp.readlines():
if line.startswith('[END]'):
break

table_entry = dict()

for column in column_list:
table_entry[column['name']
] = line[column['start']:column['end']].strip()
table_content.append(table_entry)
table_entry = {}
for column in nctable.get_column_names():
table_entry[column] = line[nctable.get_column_start(
column):nctable.get_column_end(column)].strip()
nctable.append_row(table_entry)

logging.debug('Found %d entrys', len(table_content))
return table_content
logging.debug('Found %d entrys', len(nctable.rows))
return nctable

@staticmethod
def format_entry_float(str_value):
Expand Down Expand Up @@ -134,3 +121,202 @@ def format_entry_bool(str_value):
elif str_value == '1':
return True
return False


class NCTabel():
"""generic object for table files commonly used by TNC, iTNC, CNCPILOT and
MANUALplus controls
"""

def __init__(self, name=None, suffix=None, version=None, has_unit=False, is_metric=False):
"""init object variables logging"""
logging.getLogger(__name__).addHandler(logging.NullHandler())
self.name = name
self.suffix = suffix
self.version = version
self.has_unit = has_unit
self.is_metric = is_metric
self._content = []
self._columns = []
self._column_format = {}

@property
def name(self):
"""Name of the table read from the header"""
return self._name

@name.setter
def name(self, value):
self._name = value

@property
def suffix(self):
"""file suffix of table"""
return self._suffix

@suffix.setter
def suffix(self, value):
if value is None:
self._suffix = None
else:
self._suffix = value.lower()

@property
def version(self):
"""version string of from table header"""
return self._version

@version.setter
def version(self, value):
self._version = value

@property
def has_unit(self):
"""identifies of values in table are dependent on measuring units"""
return self._has_unit

@has_unit.setter
def has_unit(self, value):
self._has_unit = value

@property
def is_metric(self):
"""if true all values sould be interpreted as metric"""
return self._is_metric

@is_metric.setter
def is_metric(self, value):
self._is_metric = value

def append_column(self, name, start, end, width=0, empty_value=None):
"""add column to the table format"""
self._columns.append(name)
if width == 0:
width = end - start
self._column_format[name] = {"width": int(
width), "start": int(start), "end": int(end)}
if empty_value is not None:
self._column_format[name]['empty_value'] = empty_value

def remove_column(self, name):
"""remove column by name from table format"""
self._columns.remove(name)
del self._column_format[name]

def get_column_start(self, name):
"""get start index of column"""
return self._column_format[name]['start']

def get_column_end(self, name):
"""get end index of column"""
return self._column_format[name]['end']

def get_column_width(self, name):
"""get width if column"""
return self._column_format[name]['width']

def get_column_empty_value(self, name):
"""get value define as default value for column"""
if 'empty_value' in self._column_format[name]:
return self._column_format[name]['empty_value']
else:
return None

def set_column_empty_value(self, name, value):
"""set the default value of a column"""
if len(str(value)) > self._column_format[name]['width']:
raise Exception('value to long for column')
self._column_format[name]['empty_value'] = value

def get_column_names(self):
"""get list of columns used in this table"""
return self._columns

def append_row(self, row):
"""add a data entry to the table"""
self._content.append(row)

def extend_rows(self, rows):
"""add multible data entries at onec"""
self._content.extend(rows)

@property
def rows(self):
"""data entries in this table"""
return self._content

def format_to_json(self):
"""return json configuration representing the table format"""
json_data = {}
json_data['version'] = self.version
json_data['suffix'] = self.suffix
json_data['column_list'] = self.get_column_names()
json_data['column_config'] = self._column_format
return json.dumps(json_data, ensure_ascii=False, indent=2)

@staticmethod
def from_json(file_path):
"""return a new NCTable object based on a json configuration file"""
with open(file_path, 'r', encoding='utf-8') as jfp:
json_data = json.load(jfp)
nct = NCTabel()
nct.version = json_data['version']
nct.suffix = json_data['suffix']
for column in json_data['column_config']:
nct.append_column(name=column,
start=json_data['column_config'][column]['start'],
end=json_data['column_config'][column]['end'])
if 'empty_value' in json_data['column_config'][column]:
nct.set_column_empty_value(
column, json_data['column_config'][column]['empty_value'])
return nct

def dump(self, file_path, renumber_column=None):
"""write table data to a file in the format used by the controls"""
row_counter = 0
file_name = file_path.name.upper()

units_string = ''
if self._has_unit:
if self._is_metric:
units_string = ' MM'
else:
units_string = ' INCH'
version_string = ''
if self._version is not None:
version_string = f' Version:{self._version:s}'

with open(file_path, 'w', encoding='ascii') as tfp:
tfp.write(f'BEGIN {file_name}{units_string}{version_string}\n')

for column_name in self._columns:
if column_name not in self._column_format:
raise Exception(
f"configuration is incomplete, missing definition for column {column_name:s}")
fixed_width = self._column_format[column_name]['width']
format_string = '{0:<%d}' % fixed_width
tfp.write(format_string.format(column_name))
tfp.write('\n')

for row in self._content:
for column_name in self._columns:
fixed_width = self._column_format[column_name]['width']
format_string = '{0:<%d}' % fixed_width

if column_name is renumber_column:
tfp.write(format_string.format(row_counter))
else:
if column_name in row:
tfp.write(format_string.format(row[column_name]))
else:
if 'empty_value' in self._column_format[column_name]:
logging.warning(
"entry is missing optional column %s defined in output format, replace with empty value", column_name)
tfp.write(format_string.format(
self._column_format[column_name]['empty_value']))
else:
raise Exception(f"entry is missing a value for column {column_name} defined in the output format")
tfp.write('\n')
row_counter += 1

tfp.write('[END]\n')
Loading