Skip to content
Open
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
32 changes: 27 additions & 5 deletions astropy/io/fits/card.py
Original file line number Diff line number Diff line change
Expand Up @@ -858,20 +858,42 @@ def _split(self):
if not m:
return kw, vc

comment = m.group("comm")
value = m.group("strg") or ""
value = value.rstrip().replace("''", "'")
if value and value[-1] == "&":
if value.endswith("&"):
value = value[:-1]

if comment is None:
trailing = vc[m.end("strg") : m.end(0)]
if trailing:
trailing_spaces = trailing.replace("'", "")
if trailing_spaces:
value += trailing_spaces

remainder = vc[m.end(0) :]
if remainder and comment is None:
extra_value = remainder.rstrip()
if extra_value.endswith("'"):
extra_value = extra_value[:-1]
extra_value = extra_value.rstrip()
if extra_value.endswith("&"):
extra_value = extra_value[:-1]
if extra_value:
value += extra_value
values.append(value)
comment = m.group("comm")
if comment:
comments.append(comment.rstrip())

if keyword in self._commentary_keywords:
valuecomment = "".join(values)
else:
# CONTINUE card
valuecomment = f"'{''.join(values)}' / {' '.join(comments)}"
joined = "".join(values)
actual_value = joined.replace("''", "'")
encoded_value = _format_value(actual_value).strip()
if comments:
valuecomment = f"{encoded_value} / {' '.join(comments)}"
else:
valuecomment = encoded_value
return keyword, valuecomment

if self.keyword in self._special_keywords:
Expand Down
63 changes: 63 additions & 0 deletions astropy/io/fits/tests/test_header.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,69 @@ def test_long_string_value_with_multiple_long_words(self):
"CONTINUE 'xml' "
)

@pytest.mark.parametrize("comment", [None, "comment"])
def test_long_string_trailing_doubled_quotes_round_trip(self, comment):
for n in range(60, 100):
value = "x" * n + "''"
if comment is None:
card = fits.Card("CONFIG", value)
image = card.image
else:
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always", VerifyWarning)
card = fits.Card("CONFIG", value, comment)
image = card.image
emitted_warning = any(
isinstance(w.message, VerifyWarning) for w in caught
)
if len(image) == card.length:
assert emitted_warning
else:
assert not emitted_warning
round_trip = fits.Card.fromstring(image)
assert round_trip.value == value
if comment is None:
assert round_trip.comment == ""
else:
if len(card.image) > card.length:
assert round_trip.comment == comment
else:
parts = card.image.split("/", 1)
expected_comment = parts[1].strip() if len(parts) == 2 else ""
assert round_trip.comment == expected_comment

def test_long_string_embedded_doubled_quotes_across_boundaries(self):
prefix = "A" * 48
suffix = "B" * 64
value = prefix + "''" + suffix
card = fits.Card("BOUND", value, "spans boundaries")
round_trip = fits.Card.fromstring(card.image)
assert round_trip.value == value
assert round_trip.comment == "spans boundaries"

@pytest.mark.parametrize(
"value, comment",
[
("x" * 100 + "''", "comment"),
("x" * 100 + "'' aaa", "comment"),
],
)
def test_long_string_regression_cases_with_trailing_quotes(self, value, comment):
card = fits.Card("FOO", value, comment)
round_trip = fits.Card.fromstring(card.image)
assert round_trip.value == value
assert round_trip.comment == comment

def test_null_string_round_trip_including_continued_value(self):
card = fits.Card("NULL", "''")
round_trip = fits.Card.fromstring(card.image)
assert round_trip.value == "''"

long_value = "prefix-" + "y" * 48 + "''" + "z" * 48 + "-suffix"
long_card = fits.Card("NULL", long_value)
long_round_trip = fits.Card.fromstring(long_card.image)
assert long_round_trip.value == long_value

def test_long_unicode_string(self):
"""Regression test for
https://github.com/spacetelescope/PyFITS/issues/1
Expand Down