Skip to content

Commit 5915980

Browse files
authored
Process the .sym file "Title" field. fixes #469 And Fix Enum dictionary comma splitting (#470)
* Implement processing of the Title property of .sym files. * Fix CanMatrix.attribute to return default as documented * Set the default for the "Title" attribute properly * Reimplement the quote_aware_comma_split function to handle spaces between fields * Change the sym parser to use quote_aware_comma_split to split enum value tables. Fixes #462. Add test cases to show that enums are read in correctly from .sym files
1 parent 09c32ea commit 5915980

File tree

5 files changed

+115
-12
lines changed

5 files changed

+115
-12
lines changed

src/canmatrix/canmatrix.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1485,10 +1485,11 @@ def attribute(self, attributeName, default=None): # type(str, typing.Any) -> ty
14851485
"""
14861486
if attributeName in self.attributes:
14871487
return self.attributes[attributeName]
1488-
else:
1489-
if attributeName in self.global_defines:
1488+
elif attributeName in self.global_defines:
14901489
define = self.global_defines[attributeName]
14911490
return define.defaultValue
1491+
else:
1492+
return default
14921493

14931494
def add_value_table(self, name, valueTable): # type: (str, typing.Mapping) -> None
14941495
"""Add named value table.

src/canmatrix/formats/sym.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,10 @@ def dump(db, f, **options): # type: (canmatrix.CanMatrix, typing.IO, **typing.A
181181
enum_dict = {}
182182
enums = "{ENUMS}\n"
183183

184-
header = """FormatVersion=5.0 // Do not edit this line!
185-
Title=\"canmatrix-Export\"
186-
"""
184+
header = """\
185+
FormatVersion=5.0 // Do not edit this line!
186+
Title=\"{}\"
187+
""".format(db.attribute("Title", "canmatrix-Export"))
187188
f.write(header.encode(sym_encoding, ignore_encoding_errors))
188189

189190
def send_receive(for_frame):
@@ -330,7 +331,11 @@ class Mode(object):
330331
# ignore empty line:
331332
if line.__len__() == 0:
332333
continue
333-
334+
if line[0:6] == "Title=":
335+
title = line[6:].strip('"')
336+
db.add_global_defines("Title", "STRING")
337+
db.global_defines['Title'].set_default("canmatrix-Export")
338+
db.add_attribute("Title", title)
334339
# switch mode:
335340
if line[0:7] == "{ENUMS}":
336341
mode = Mode.enums
@@ -363,7 +368,7 @@ class Mode(object):
363368
line = line.split('//')[0]
364369
temp_array = line[5:].strip().rstrip(')').split('(', 1)
365370
val_table_name = temp_array[0]
366-
split = canmatrix.utils.quote_aware_space_split(temp_array[1])
371+
split = canmatrix.utils.quote_aware_comma_split(temp_array[1])
367372
temp_array = [s.rstrip(',') for s in split]
368373
temp_val_table = {}
369374
for entry in temp_array:

src/canmatrix/tests/test_sym.py

+45
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,48 @@ def test_unterminated_enum():
173173
else:
174174
assert isinstance(matrix.load_errors[0], StopIteration)
175175

176+
177+
def test_title_read_and_write():
178+
f = io.BytesIO(
179+
textwrap.dedent(
180+
'''\
181+
FormatVersion=5.0 // Do not edit this line!
182+
Title="An Example Title"
183+
184+
'''
185+
).encode('utf-8'),
186+
)
187+
188+
matrix = canmatrix.formats.sym.load(f)
189+
assert matrix.attribute("Title") == "An Example Title"
190+
f_out = io.BytesIO()
191+
canmatrix.formats.sym.dump(matrix, f_out)
192+
assert f_out.getvalue().decode('utf-8').splitlines()[1] == 'Title="An Example Title"'
193+
194+
@pytest.mark.parametrize(
195+
'enum_str, enum_dict, enum_label',
196+
(
197+
('enum Animal(0="Dog", 1="Cat", 2="Fox")', {"Animal": {0: "Dog", 1: "Cat", 2: "Fox"}}, "Simple enum"),
198+
('''\
199+
enum Animal(0="Dog", //A Comment
200+
1="Cat",
201+
2="Fox")''',
202+
{"Animal": {0: "Dog", 1: "Cat", 2: "Fox"}}, "Multiline enum"),
203+
('enum Animal(0="Dog",1="Cat",2="Fox")', {"Animal": {0: "Dog", 1: "Cat", 2: "Fox"}}, "No Space in Separator"),
204+
)
205+
)
206+
def test_enums_read(enum_str, enum_dict, enum_label):
207+
f = io.BytesIO('''\
208+
FormatVersion=5.0 // Do not edit this line!
209+
Title="An Example Title"
210+
211+
{{ENUMS}}
212+
{}
213+
'''.format(enum_str).encode('utf-8'),
214+
)
215+
216+
matrix = canmatrix.formats.sym.load(f)
217+
assert matrix.load_errors == [], "Failed to load canmatrix, when testing enum case : '{}'".format(enum_label)
218+
assert matrix.value_tables == enum_dict, "Enum not parsed correctly : '{}'".format(enum_label)
219+
f_out = io.BytesIO()
220+
canmatrix.formats.sym.dump(matrix, f_out)

src/canmatrix/tests/test_utils.py

+26
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- coding: utf-8 -*-
2+
import pytest
3+
24
import canmatrix.utils
35

46

@@ -10,7 +12,31 @@ def test_utils_guess_value():
1012
assert canmatrix.utils.guess_value("False") == "0"
1113
assert canmatrix.utils.guess_value("faLse") == "0"
1214

15+
1316
def test_decode_number():
1417
assert canmatrix.utils.decode_number("0x10") == 16
1518
assert canmatrix.utils.decode_number("0b10") == 2
1619
assert canmatrix.utils.decode_number("10") == 10
20+
21+
22+
@pytest.mark.parametrize(
23+
'input_string, expected_list',
24+
(
25+
('a,b,c,d',
26+
["a", "b", "c", "d"]),
27+
28+
('a, b, c, d',
29+
["a", "b", "c", "d"]),
30+
31+
('a, b", c", "d"',
32+
['a', 'b", c"', 'd']),
33+
34+
('0="a", 1=b, 3="c"d, 4=e',
35+
['0="a"', '1=b', '3="c"d', '4=e']),
36+
37+
('"a,b",","b,c","\'\'d"e',
38+
['a,b', '","b', 'c","\'\'d\"e']),
39+
)
40+
)
41+
def test_quote_aware_comma_split_function(input_string, expected_list):
42+
assert canmatrix.utils.quote_aware_comma_split(input_string) == expected_list

src/canmatrix/utils.py

+31-5
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,37 @@ def quote_aware_space_split(in_line): # type: (str) -> typing.List[str]
2121

2222

2323
def quote_aware_comma_split(string): # type: (str) -> typing.List[str]
24-
if sys.version_info >= (3, 0):
25-
temp = list(csv.reader([string], skipinitialspace=True))
26-
else:
27-
temp = list(csv.reader([string.encode("utf8")], skipinitialspace=True))
28-
return temp[0]
24+
"""
25+
Split a string containing comma separated list of fields.
26+
Removing surrounding whitespace, to allow fields to be separated by ", ".
27+
Preserves double quotes within fields, but not double quotes surrounding fields.
28+
Suppresses comma separators which are within double quoted sections.
29+
:param string: ('a, b", c", "d"',
30+
:return: ['a', 'b", c"', 'd']),
31+
"""
32+
fields = []
33+
quoted = False
34+
field = ""
35+
# Separate string by unquoted commas
36+
for char in string:
37+
if char == ',':
38+
if not quoted:
39+
fields.append(field)
40+
field = ""
41+
continue
42+
if char == '"':
43+
quoted = not quoted
44+
field += char
45+
if field:
46+
fields.append(field)
47+
# Remove surrounding whitespace from fields
48+
fields = [f.strip() for f in fields]
49+
# Remove "" that surround entire fields
50+
for i, f in enumerate(fields):
51+
if len(f) > 1:
52+
if f.startswith('"') and f.endswith('"'):
53+
fields[i] = f[1:-1]
54+
return fields
2955

3056

3157
def guess_value(text_value): # type: (str) -> str

0 commit comments

Comments
 (0)