Skip to content

Commit 38870b8

Browse files
committed
Allow for trailing whitespace in categories
If a line like "_cat1.foo bar" has trailing whitespace before the newline, this is valid mmCIF, so capture the whitespace in the token group rather than reporting an error.
1 parent 5a8366d commit 38870b8

2 files changed

Lines changed: 31 additions & 7 deletions

File tree

ihm/format.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -570,15 +570,18 @@ def _skip_initial_whitespace(self, line, strlen, start_pos):
570570

571571
class _CategoryTokenGroup:
572572
"""A group of tokens which set a single data item"""
573-
def __init__(self, vartoken, valtoken):
573+
def __init__(self, vartoken, valtoken, whitespace):
574574
self.vartoken, self.valtoken = vartoken, valtoken
575+
self.whitespace = whitespace
575576

576577
def __str__(self):
577578
return ("<_CategoryTokenGroup(%s, %s)>"
578579
% (self.vartoken.as_mmcif(), self.valtoken.token.as_mmcif()))
579580

580581
def as_mmcif(self):
581-
return self.vartoken.as_mmcif() + self.valtoken.as_mmcif() + "\n"
582+
return (self.vartoken.as_mmcif() + self.valtoken.as_mmcif()
583+
+ (self.whitespace.as_mmcif() if self.whitespace else "")
584+
+ "\n")
582585

583586
def __set_value(self, val):
584587
self.valtoken.value = val
@@ -1037,11 +1040,17 @@ def _read_value(self, vartoken):
10371040
"No valid value found for %s.%s on line %d"
10381041
% (vartoken.category, vartoken.keyword, self._linenum))
10391042
eoltok = self._get_token()
1043+
# Handle whitespace at the end of the line, if any
1044+
if isinstance(eoltok, _WhitespaceToken):
1045+
whitespace = eoltok
1046+
eoltok = self._get_token()
1047+
else:
1048+
whitespace = None
10401049
if not isinstance(eoltok, _EndOfLineToken):
10411050
raise CifParserError(
10421051
"No end of line after %s.%s on line %d"
10431052
% (vartoken.category, vartoken.keyword, self._linenum))
1044-
return _CategoryTokenGroup(vartoken, spval)
1053+
return _CategoryTokenGroup(vartoken, spval, whitespace)
10451054

10461055
def _read_loop(self, looptoken):
10471056
"""Handle a loop_ construct"""

test/test_format.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,21 +1114,27 @@ def test_cif_token_reader(self):
11141114
cif = """
11151115
data_foo_bar
11161116
#
1117-
_cat1.foo ?
1117+
_cat1.foo ?
11181118
#
11191119
loop_
11201120
_foo.bar
11211121
_foo.baz
11221122
a b c d
11231123
x y
1124-
"""
1124+
_cat2.bar ?
1125+
""" # noqa: W291
11251126
r = ihm.format.CifTokenReader(StringIO(cif))
11261127
tokens = list(r.read_file())
11271128
self.assertIsInstance(tokens[5], ihm.format._CategoryTokenGroup)
1129+
# cat1.foo has trailing whitespace
1130+
self.assertEqual(tokens[5].whitespace.as_mmcif(), " ")
11281131
self.assertIsInstance(tokens[8], ihm.format._LoopHeaderTokenGroup)
11291132
self.assertIsInstance(tokens[9], ihm.format._LoopRowTokenGroup)
11301133
self.assertIsInstance(tokens[10], ihm.format._LoopRowTokenGroup)
11311134
self.assertIsInstance(tokens[11], ihm.format._LoopRowTokenGroup)
1135+
self.assertIsInstance(tokens[13], ihm.format._CategoryTokenGroup)
1136+
# cat2.bar has no trailing whitespace
1137+
self.assertIsNone(tokens[13].whitespace)
11321138
new_cif = "".join(x.as_mmcif() for x in tokens)
11331139
self.assertEqual(new_cif, cif)
11341140

@@ -1138,6 +1144,12 @@ def test_cif_token_reader_missing_value(self):
11381144
r = ihm.format.CifTokenReader(StringIO(cif))
11391145
self.assertRaises(ihm.format.CifParserError, list, r.read_file())
11401146

1147+
def test_cif_token_reader_category_no_eol(self):
1148+
"""Category must end in end-of-line"""
1149+
cif = '_foo.bar foo bar baz\n'
1150+
r = ihm.format.CifTokenReader(StringIO(cif))
1151+
self.assertRaises(ihm.format.CifParserError, list, r.read_file())
1152+
11411153
def test_cif_token_reader_loop_mixed_categories(self):
11421154
"""Test bad mmCIF loop with a mix of categories"""
11431155
cif = 'loop_\n_atom_site.id\n_foo.bar\n'
@@ -1338,12 +1350,15 @@ def test_category_token_group(self):
13381350
space = ihm.format._WhitespaceToken(" ")
13391351
val = ihm.format._TextValueToken("baz", quote=None)
13401352
tg = ihm.format._CategoryTokenGroup(
1341-
var, ihm.format._SpacedToken([space], val))
1353+
var, ihm.format._SpacedToken([space], val), None)
13421354
self.assertEqual(str(tg), "<_CategoryTokenGroup(_foo.bar, baz)>")
13431355
self.assertEqual(tg.as_mmcif(), '_foo.bar baz\n')
13441356
self.assertEqual(tg.category, "_foo")
13451357
self.assertEqual(tg.keyword, "bar")
13461358
self.assertEqual(tg.value, "baz")
1359+
tg = ihm.format._CategoryTokenGroup(
1360+
var, ihm.format._SpacedToken([space], val), space)
1361+
self.assertEqual(tg.as_mmcif(), '_foo.bar baz \n')
13471362
tg.value = None
13481363
self.assertIsNone(tg.value)
13491364

@@ -1406,7 +1421,7 @@ def test_change_value_filter_category(self):
14061421
space = ihm.format._WhitespaceToken(" ")
14071422
val = ihm.format._TextValueToken("baz", quote=None)
14081423
tg = ihm.format._CategoryTokenGroup(
1409-
var, ihm.format._SpacedToken([space], val))
1424+
var, ihm.format._SpacedToken([space], val), None)
14101425
# Value does not match
14111426
f = ihm.format.ChangeValueFilter("_foo.bar", old='old', new='new')
14121427
new_tg = f.filter_category(tg)

0 commit comments

Comments
 (0)