109
376
r'(?P<comm>(.|\n)*)'
112
# keys of commentary cards
113
_commentary_keys = ['', 'COMMENT', 'HISTORY']
115
def __new__(cls, key='', value='', comment=''):
117
Return the appropriate Card subclass depending on they key and/or
122
val_str = _value_to_string(value)
123
if len(val_str) > (Card.length - 10):
124
klass = _ContinueCard
125
elif isinstance(key, basestring) and key.upper().strip() == 'HIERARCH':
126
klass = _HierarchCard
127
return super(Card, cls).__new__(klass)
129
def __init__(self, key='', value='', comment=''):
131
Construct a card from `key`, `value`, and (optionally) `comment`.
132
Any specifed arguments, except defaults, must be compliant to FITS
140
value : str, optional
143
comment : str, optional
147
if key != '' or value != '' or comment != '':
149
self._update_value(value)
150
self._update_comment(comment)
151
# for commentary cards, value can only be strings and there
153
if self.key in Card._commentary_keys:
154
if not isinstance(self.value, basestring):
155
raise ValueError('Value in a commentary card must be a '
158
self._cardimage = ' ' * 80
379
_rvkc_identifier = r'[a-zA-Z_]\w*'
380
_rvkc_field = _rvkc_identifier + r'(\.\d+)?'
381
_rvkc_field_specifier_s = r'%s(\.%s)*' % ((_rvkc_field,) * 2)
382
_rvkc_field_specifier_val = (r'(?P<keyword>%s): (?P<val>%s\s*)' %
383
(_rvkc_field_specifier_s, _numr_FSC))
384
_rvkc_keyword_val = r'\'%s\'' % _rvkc_field_specifier_val
385
_rvkc_keyword_val_comm = (r' +%s *(/ *(?P<comm>[ -~]*))?$' %
388
_rvkc_field_specifier_val_RE = re.compile(_rvkc_field_specifier_val)
390
# regular expression to extract the key and the field specifier from a
391
# string that is being used to index into a card list that contains
392
# record value keyword cards (ex. 'DP1.AXIS.1')
393
_rvkc_keyword_name_RE = (
394
re.compile(r'(?P<keyword>%s)\.(?P<field_specifier>%s)$' %
395
(_rvkc_identifier, _rvkc_field_specifier_s)))
397
# regular expression to extract the field specifier and value and comment
398
# from the string value of a record value keyword card
399
# (ex "'AXIS.1: 1' / a comment")
400
_rvkc_keyword_val_comm_RE = re.compile(_rvkc_keyword_val_comm)
402
_commentary_keywords = ['', 'COMMENT', 'HISTORY', 'END']
404
def __init__(self, keyword=None, value=None, comment=None, **kwargs):
405
# For backwards compatibility, support the 'key' keyword argument:
406
if keyword is None and 'key' in kwargs:
407
keyword = kwargs['key']
414
# This attribute is set to False when creating the card from a card
415
# image to ensure that the contents of the image get verified at some
417
self._verified = True
419
# A flag to conveniently mark whether or not this was a valid HIERARCH
421
self._hierarch = False
423
# If the card could not be parsed according the the FITS standard or
424
# any recognized non-standard conventions, this will be True
425
self._invalid = False
427
self._field_specifier = None
428
if not self._check_if_rvkc(keyword, value):
429
# If _check_if_rvkc passes, it will handle setting the keyword and
431
if keyword is not None:
432
self.keyword = keyword
433
if value is not None:
436
if comment is not None:
437
self.comment = comment
159
439
self._modified = False
440
self._valuestring = None
441
self._valuemodified = False
444
return repr((self.keyword, self.value, self.comment))
161
446
def __str__(self):
162
return self.cardimage
452
def __getitem__(self, index):
453
return (self.keyword, self.value, self.comment)[index]
165
457
"""Returns the keyword name parsed from the card image."""
167
if not hasattr(self, '_key'):
168
head = self._get_key_string()
169
self._key = head.strip().upper()
172
def _setkey(self, val):
458
if self._keyword is not None:
461
self._keyword = self._parse_keyword()
468
def keyword(self, keyword):
173
469
"""Set the key attribute; once set it cannot be modified."""
175
if hasattr(self, '_key'):
176
raise AttributeError('Keyword name cannot be modified.')
178
if isinstance(val, basestring):
470
if self._keyword is not None:
471
raise AttributeError(
472
'Once set, the Card keyword may not be modified')
473
elif isinstance(keyword, basestring):
474
# Be nice and remove trailing whitespace--some FITS code always
475
# pads keywords out with spaces; leading whitespace, however,
476
# should be strictly disallowed.
477
keyword = keyword.rstrip()
478
keyword_upper = keyword.upper()
479
if len(keyword) <= 8 and self._keywd_FSC_RE.match(keyword_upper):
480
# For keywords with length > 8 they will be HIERARCH cards,
481
# and can have arbitrary case keywords
482
if keyword_upper == 'END':
183
483
raise ValueError("Keyword 'END' not allowed.")
484
keyword = keyword_upper
485
elif self._keywd_hierarch_RE.match(keyword):
486
# In prior versions of PyFITS HIERARCH cards would only be
487
# created if the user-supplied keyword explicitly started with
488
# 'HIERARCH '. Now we will create them automtically for long
489
# keywords, but we still want to support the old behavior too;
490
# the old behavior makes it possible to create HEIRARCH cards
491
# that would otherwise be recognized as RVKCs
492
self._hierarch = True
494
if keyword_upper[:9] == 'HIERARCH ':
495
# The user explicitly asked for a HIERARCH card, so don't
496
# bug them about it...
497
keyword = keyword[9:]
499
# We'll gladly create a HIERARCH card, but a warning is
502
'Keyword name %r is greater than 8 characters or '
503
'or contains spaces; a HIERARCH card will be created.' %
504
keyword, VerifyWarning)
186
if val[:8].upper() == 'HIERARCH':
187
val = val[8:].strip()
188
self.__class__ = _HierarchCard
190
raise ValueError('Keyword name %s is too long (> 8), use HIERARCH.'
193
raise ValueError('Keyword name %s is not a string.' % repr(val))
195
self._modified = True
197
# TODO: It would be nice to eventually use property.getter/setter/deleter,
198
# but those are not available prior to Python 2.6
199
key = property(_getkey, _setkey, doc='Card keyword')
202
"""Get the value attribute from the card image if not already set."""
204
if not hasattr(self, '_value'):
205
# self._value assigned directly to avoid the checks in
206
# _setvalue() (and to avoid setting _value_modified = True)
207
self._value = self._extract_value()
210
def _setvalue(self, val):
211
self._update_value(val)
212
if hasattr(self, '_cardimage'):
213
# Make sure these are saved from the old cardimage before deleting
219
value = property(_getvalue, _setvalue, doc='Card value')
221
def _update_value(self, val):
222
"""Set the value attribute."""
224
if isinstance(val, (str, int, long, float, complex, bool, Undefined,
225
np.floating, np.integer, np.complexfloating,
227
if isinstance(val, str):
228
self._check_text(val)
229
if not hasattr(self, '_value') or self._value != val:
230
self._modified = True
231
self._value_modified = True
234
raise ValueError('Illegal value %s.' % repr(val))
236
def _getcomment(self):
506
raise ValueError('Illegal keyword name: %r.' % keyword)
507
self._keyword = keyword
508
self._modified = True
510
raise ValueError('Keyword name %r is not a string.' % keyword)
513
@deprecated('3.1', alternative='the `.keyword` attribute')
519
if self.field_specifier:
520
return float(self._value)
522
if self._value is not None:
524
elif self._valuestring is not None or self._image:
525
self._value = self._parse_value()
528
self._value = value = ''
530
if pyfits.STRIP_HEADER_WHITESPACE and isinstance(value, basestring):
531
value = value.rstrip()
536
def value(self, value):
539
'The value of invalid/unparseable cards cannot set. Either '
540
'delete this card from the header or replace it.')
544
oldvalue = self._value
548
if not isinstance(value, (basestring, int, long, float, complex, bool,
549
Undefined, np.floating, np.integer,
550
np.complexfloating, np.bool_)):
551
raise ValueError('Illegal value: %r.' % value)
553
if isinstance(value, unicode):
555
# Any string value must be encodable as ASCII
556
value.encode('ascii')
557
except UnicodeEncodeError:
559
'FITS header values must contain standard ASCII '
560
'characters; %r contains characters not representable in '
563
if (pyfits.STRIP_HEADER_WHITESPACE and
564
(isinstance(oldvalue, basestring) and
565
isinstance(value, basestring))):
566
# Ignore extra whitespace when comparing the new value to the old
567
different = oldvalue.rstrip() != value.rstrip()
569
different = oldvalue != value
573
self._modified = True
574
self._valuestring = None
575
self._valuemodified = True
576
if self.field_specifier:
578
self._value = _int_or_float(self._value)
580
raise ValueError('value %s is not a float' %
587
'The value of invalid/unparseable cards cannot deleted. '
588
'Either delete this card from the header or replace it.')
590
if not self.field_specifier:
593
raise AttributeError('Values cannot be deleted from record-valued '
237
598
"""Get the comment attribute from the card image if not already set."""
239
if not hasattr(self, '_comment'):
240
self._comment = self._extract_comment()
243
def _setcomment(self, val):
244
self._update_comment(val)
245
if hasattr(self, '_cardimage'):
246
# Make sure these are saved from the old cardimage before deleting
252
comment = property(_getcomment, _setcomment, doc='Card comment')
254
def _update_comment(self, val):
255
"""Set the comment attribute."""
257
if isinstance(val, basestring):
258
self._check_text(val)
261
raise ValueError('Comment %s is not a string.' % repr(val))
262
if not hasattr(self, '_comment') or self._comment != val:
263
self._modified = True
600
if self._comment is not None:
603
self._comment = self._parse_comment()
610
def comment(self, comment):
613
'The comment of invalid/unparseable cards cannot set. Either '
614
'delete this card from the header or replace it.')
619
if isinstance(comment, unicode):
621
# Any string value must be encodable as ASCII
622
comment.encode('ascii')
623
except UnicodeEncodeError:
625
'FITS header comments must contain standard ASCII '
626
'characters; %r contains characters not representable in '
629
oldcomment = self._comment
630
if oldcomment is None:
632
if comment != oldcomment:
633
self._comment = comment
634
self._modified = True
640
'The comment of invalid/unparseable cards cannot deleted. '
641
'Either delete this card from the header or replace it.')
646
def field_specifier(self):
648
The field-specifier of record-valued keyword cards; always `None` on
652
# Ensure that the keyword exists and has been parsed--the will set the
653
# internal _field_specifier attribute if this is a RVKC.
655
return self._field_specifier
659
@field_specifier.setter
660
def field_specifier(self, field_specifier):
661
if not field_specifier:
662
raise ValueError('The field-specifier may not be blank in '
663
'record-valued keyword cards.')
664
elif not self.field_specifier:
665
raise AttributeError('Cannot coerce cards to be record-valued '
666
'keyword cards by setting the '
667
'field_specifier attribute')
668
elif field_specifier != self.field_specifier:
669
self._field_specifier = field_specifier
670
# The keyword need also be updated
671
keyword = self._keyword.split('.', 1)[0]
672
self._keyword = '.'.join([keyword, field_specifier])
673
self._modified = True
675
@field_specifier.deleter
676
def field_specifier(self):
677
raise AttributeError('The field_specifier attribute may not be '
678
'deleted from record-valued keyword cards.')
682
if self._image and not self._verified:
684
if self._image is None or self._modified:
685
self._image = self._format_image()
689
@deprecated('3.1', alternative='the `.image` attribute')
267
690
def cardimage(self):
268
if not hasattr(self, '_cardimage'):
270
return self._cardimage
272
def _update_cardimage(self):
273
"""This used to do what _generate_cardimage() does, plus setting te
274
value of self._cardimage. Now it's kept mostly for symmetry.
277
self._cardimage = self._generate_cardimage()
279
def _generate_cardimage(self):
280
"""Generate a (new) card image from the attributes: `key`, `value`,
281
and `comment`. Core code for `ascardimage`.
284
key_str = self._format_key()
285
val_str = self._format_value()
286
is_commentary = key_str.strip() in Card._commentary_keys
290
comment_str = self._format_comment()
294
if is_commentary: # not using self.key
296
if hasattr(self, '_value'):
297
# Value must be a string in commentary cards
298
val_str = str(self.value)
300
# put all parts together
301
output = ''.join([key_str, eq_str, val_str, comment_str])
303
# need this in case card-with-continue's value is shortened
304
if not isinstance(self, _HierarchCard) and \
305
not isinstance(self, RecordValuedKeywordCard):
306
self.__class__ = Card
308
key_val_len = len(key_str) + len(eq_str) + len(val_str)
309
if key_val_len > Card.length:
310
if isinstance(self, _HierarchCard) and \
311
key_val_len == Card.length + 1 and \
313
output = ''.join([key_str[:-1], eq_str, val_str,
316
raise ValueError('The keyword %s with its value is too '
319
if len(output) <= Card.length:
320
output = '%-80s' % output
322
# longstring case (CONTINUE card)
324
# try not to use CONTINUE if the string value can fit in one line.
325
# Instead, just truncate the comment
326
if isinstance(self.value, str) and \
327
len(val_str) > (Card.length - 10):
328
self.__class__ = _ContinueCard
329
output = self._breakup_strings()
331
warnings.warn('Card is too long, comment is truncated.')
332
output = output[:Card.length]
336
def _format_key(self):
337
# keyword string (check both _key and _cardimage attributes to avoid an
339
if hasattr(self, '_key') or hasattr(self, '_cardimage'):
340
return '%-8s' % self.key
344
def _format_value(self):
346
if not (hasattr(self, '_value') or hasattr(self, '_cardimage')):
348
elif (not hasattr(self, '_value_modified') or
349
not self._value_modified) and \
350
isinstance(self.value, (float, np.floating, complex,
351
np.complexfloating)):
352
# Keep the existing formatting for float/complex numbers
353
return '%20s' % self._valuestring
355
return _value_to_string(self.value)
357
def _format_comment(self):
359
if hasattr(self, '_comment') or hasattr(self, '_cardimage'):
363
return ' / ' + self._comment
367
# TODO: Wouldn't 'verification' be a better name for the 'option' keyword
368
# argument? 'option' is pretty vague.
369
@deprecated(alternative='the .cardimage attribute')
693
@deprecated('3.1', alternative='the `.image` attribute')
370
694
def ascardimage(self, option='silentfix'):
371
return self._ascardimage(option)
373
def _ascardimage(self, option='silentfix'):
375
Generate a (new) card image from the attributes: `key`, `value`,
376
and `comment`, or from raw string.
381
Output verification option. Must be one of ``"fix"``,
382
``"silentfix"``, ``"ignore"``, ``"warn"``, or
383
``"exception"``. See :ref:`verify` for more info.
386
# Only if the card image already exist (to avoid infinite loop),
388
if hasattr(self, '_cardimage'):
390
self._update_cardimage()
391
return self._cardimage
695
if not self._verified:
394
def fromstring(cls, cardimage):
396
Construct a `Card` object from a (raw) string. It will pad the
397
string if it is not the length of a card image (80 columns).
398
If the card image is longer than 80 columns, assume it
399
contains ``CONTINUE`` card(s).
402
cardimage = _pad(cardimage)
404
if cardimage[:8].upper() == 'HIERARCH':
405
card = _HierarchCard()
406
# for card image longer than 80, assume it contains CONTINUE card(s).
407
elif len(cardimage) > Card.length:
408
card = _ContinueCard()
412
card._cardimage = cardimage
700
def fromstring(cls, image):
702
Construct a `Card` object from a (raw) string. It will pad the string
703
if it is not the length of a card image (80 columns). If the card
704
image is longer than 80 columns, assume it contains ``CONTINUE``
709
card._image = _pad(image)
710
card._verified = False
415
def _check_text(self, val):
416
"""Verify `val` to be printable ASCII text."""
418
if Card._comment_FSC_RE.match(val) is None:
419
self._err_text = 'Unprintable string %s' % repr(val)
420
self._fixable = False
421
raise ValueError(self._err_text)
423
def _check_key(self, key):
424
"""Verify the keyword `key` to be FITS standard."""
426
# use repr (not str) in case of control character
427
if not Card._keywd_FSC_RE.match(key):
428
self._err_text = 'Illegal keyword name %s' % repr(key)
429
self._fixable = False
430
raise ValueError(self._err_text)
432
def _extract_value(self):
714
def normalize_keyword(cls, keyword):
716
`classmethod` to convert a keyword value that may contain a
717
field-specifier to uppercase. The effect is to raise the key to
718
uppercase and leave the field specifier in its original case.
723
A keyword value or a ``keyword.field-specifier`` value
730
match = cls._rvkc_keyword_name_RE.match(keyword)
733
return '.'.join((match.group('keyword').strip().upper(),
734
match.group('field_specifier')))
735
elif len(keyword) > 9 and keyword[:9].upper() == 'HIERARCH ':
736
# Remove 'HIERARCH' from HIERARCH keywords; this could lead to
737
# ambiguity if there is actually a keyword card containing
738
# "HIERARCH HIERARCH", but shame on you if you do that.
739
return keyword[9:].strip()
741
return keyword.strip().upper()
743
def _check_if_rvkc(self, *args):
745
Determine whether or not the card is a record-valued keyword card.
747
If one argument is given, that argument is treated as a full card image
748
and parsed as such. If two arguments are given, the first is treated
749
as the card keyword (including the field-specifier if the card is
750
intened as a RVKC), and the second as the card value OR the first value
751
can be the base keyword, and the second value the 'field-specifier:
754
If the check passes the ._keyword, ._value, and .field_specifier
760
>>> self._check_if_rvkc('DP1', 'AXIS.1: 2')
761
>>> self._check_if_rvkc('DP1.AXIS.1', 2)
762
>>> self._check_if_rvkc('DP1 = AXIS.1: 2')
765
if not pyfits.ENABLE_RECORD_VALUED_KEYWORD_CARDS:
770
eq_idx = image.find('=')
771
if eq_idx < 0 or eq_idx > 9:
773
keyword, rest = image.split('=', 1)
774
match = self._rvkc_keyword_val_comm_RE.match(rest)
776
field_specifier = match.group('keyword')
777
self._keyword = '.'.join((keyword.strip().upper(),
779
self._field_specifier = field_specifier
780
self._value = _int_or_float(match.group('val'))
783
keyword, value = args
784
if not isinstance(keyword, basestring):
786
match = self._rvkc_keyword_name_RE.match(keyword)
787
if match and isinstance(value, (int, float)):
788
field_specifier = match.group('field_specifier')
789
self._keyword = '.'.join((match.group('keyword').upper(),
791
self._field_specifier = field_specifier
794
if isinstance(value, basestring):
795
match = self._rvkc_field_specifier_val_RE.match(value)
796
if match and self._keywd_FSC_RE.match(keyword):
797
field_specifier = match.group('keyword')
798
self._keyword = '.'.join((keyword.upper(),
800
self._field_specifier = field_specifier
801
self._value = _int_or_float(match.group('val'))
804
def _parse_keyword(self):
805
if self._check_if_rvkc(self._image):
808
keyword = self._image[:8].strip()
809
keyword_upper = keyword.upper()
810
value_indicator = self._image.find('= ')
812
special = self._commentary_keywords + ['CONTINUE']
814
if keyword_upper in special or 0 <= value_indicator <= 8:
815
# The value indicator should appear in byte 8, but we are flexible
816
# and allow this to be fixed
817
if value_indicator >= 0:
818
keyword = keyword[:value_indicator]
819
keyword_upper = keyword_upper[:value_indicator]
821
if keyword_upper != keyword:
822
self._modified = True
824
elif (keyword_upper == 'HIERARCH' and self._image[8] == ' ' and
826
# This is valid HIERARCH card as described by the HIERARCH keyword
828
# http://fits.gsfc.nasa.gov/registry/hierarch_keyword.html
829
self._hierarch = True
830
return self._image.split('=', 1)[0][9:].rstrip()
832
warnings.warn('The following header keyword is invalid or follows '
833
'an unrecognized non-standard convention:\n%s' %
838
def _parse_value(self):
433
839
"""Extract the keyword value from the card image."""
435
841
# for commentary cards, no need to parse further
436
if self.key in Card._commentary_keys:
437
return self.cardimage[8:].rstrip()
439
valu = self._check(option='parse')
442
raise ValueError("Unparsable card (%s), fix it first with "
443
".verify('fix')." % self.key)
444
if valu.group('bool') is not None:
445
_val = valu.group('bool') == 'T'
446
elif valu.group('strg') is not None:
447
_val = re.sub("''", "'", valu.group('strg'))
448
elif valu.group('numr') is not None:
842
# Likewise for invalid cards
843
if self.keyword.upper() in self._commentary_keywords or self._invalid:
844
return self._image[8:].rstrip()
846
if self._check_if_rvkc(self._image):
849
if len(self._image) > self.length:
851
for card in self._itersubcards():
852
value = card.value.rstrip().replace("''", "'")
853
if value and value[-1] == '&':
857
value = ''.join(values)
859
self._valuestring = value
862
m = self._value_NFSC_RE.match(self._split()[1])
865
raise VerifyError("Unparsable card (%s), fix it first with "
866
".verify('fix')." % self.key)
868
if m.group('bool') is not None:
869
value = m.group('bool') == 'T'
870
elif m.group('strg') is not None:
871
value = re.sub("''", "'", m.group('strg'))
872
elif m.group('numr') is not None:
449
873
# Check for numbers with leading 0s.
450
numr = Card._number_NFSC_RE.match(valu.group('numr'))
451
_digt = translate(numr.group('digt'), FIX_FP_TABLE2, ' ')
874
numr = self._number_NFSC_RE.match(m.group('numr'))
875
digt = translate(numr.group('digt'), FIX_FP_TABLE2, ' ')
452
876
if numr.group('sign') is None:
455
_sign = numr.group('sign')
456
_val = _str_to_num(_sign + _digt)
879
sign = numr.group('sign')
880
value = _str_to_num(sign + digt)
458
elif valu.group('cplx') is not None:
882
elif m.group('cplx') is not None:
459
883
# Check for numbers with leading 0s.
460
real = Card._number_NFSC_RE.match(valu.group('real'))
461
_rdigt = translate(real.group('digt'), FIX_FP_TABLE2, ' ')
884
real = self._number_NFSC_RE.match(m.group('real'))
885
rdigt = translate(real.group('digt'), FIX_FP_TABLE2, ' ')
462
886
if real.group('sign') is None:
465
_rsign = real.group('sign')
466
_val = _str_to_num(_rsign + _rdigt)
467
imag = Card._number_NFSC_RE.match(valu.group('imag'))
468
_idigt = translate(imag.group('digt'), FIX_FP_TABLE2, ' ')
889
rsign = real.group('sign')
890
value = _str_to_num(rsign + rdigt)
891
imag = self._number_NFSC_RE.match(m.group('imag'))
892
idigt = translate(imag.group('digt'), FIX_FP_TABLE2, ' ')
469
893
if imag.group('sign') is None:
472
_isign = imag.group('sign')
473
_val += _str_to_num(_isign + _idigt)*1j
896
isign = imag.group('sign')
897
value += _str_to_num(isign + idigt) * 1j
477
if not hasattr(self, '_valuestring'):
478
self._valuestring = valu.group('valu')
479
if not hasattr(self, '_value_modified'):
480
self._value_modified = False
483
def _extract_comment(self):
901
if not self._valuestring:
902
self._valuestring = m.group('valu')
905
def _parse_comment(self):
484
906
"""Extract the keyword value from the card image."""
486
908
# for commentary cards, no need to parse further
487
if self.key in Card._commentary_keys:
909
# likewise for invalid/unparseable cards
910
if self.keyword in Card._commentary_keywords or self._invalid:
490
valu = self._check(option='parse')
492
comm = valu.group('comm')
493
if isinstance(comm, str):
913
if len(self._image) > self.length:
915
for card in self._itersubcards():
917
comments.append(card.comment)
918
comment = '/ ' + ' '.join(comments).rstrip()
919
m = self._value_NFSC_RE.match(comment)
921
m = self._value_NFSC_RE.match(self._split()[1])
924
comment = m.group('comm')
926
return comment.rstrip()
497
def _fix_value(self, input):
931
Split the card image between the keyword and the rest of the card.
934
if self._image is not None:
935
# If we already have a card image, don't try to rebuild a new card
936
# image, which self.image would do
941
if self.keyword in self._commentary_keywords + ['CONTINUE']:
942
keyword, valuecomment = image.split(' ', 1)
945
delim_index = image.index('= ')
949
# The equal sign may not be any higher than column 10; anything
950
# past that must be considered part of the card value
951
if delim_index is None:
953
valuecomment = image[8:]
954
elif delim_index > 10 and image[:9] != 'HIERARCH ':
956
valuecomment = image[8:]
958
keyword, valuecomment = image.split('= ', 1)
959
return keyword.strip(), valuecomment.strip()
961
def _fix_keyword(self):
962
if self.field_specifier:
963
keyword, field_specifier = self._keyword.split('.', 1)
964
self._keyword = '.'.join([keyword.upper(), field_specifier])
966
self._keyword = self._keyword.upper()
967
self._modified = True
969
def _fix_value(self):
498
970
"""Fix the card image for fixable non-standard compliance."""
973
keyword, valuecomment = self._split()
974
m = self._value_NFSC_RE.match(valuecomment)
502
976
# for the unparsable case
504
_tmp = self._get_value_comment_string()
506
slash_loc = _tmp.index("/")
507
self._value = _tmp[:slash_loc].strip()
508
self._comment = _tmp[slash_loc + 1:].strip()
510
self._value = _tmp.strip()
512
elif input.group('numr') is not None:
513
numr = Card._number_NFSC_RE.match(input.group('numr'))
514
_val_str = translate(numr.group('digt'), FIX_FP_TABLE, ' ')
979
value, comment = valuecomment.split('/', 1)
980
self.value = value.strip()
981
self.comment = comment.strip()
982
except (ValueError, IndexError):
983
self.value = valuecomment
984
self._valuestring = self._value
986
elif m.group('numr') is not None:
987
numr = self._number_NFSC_RE.match(m.group('numr'))
988
value = translate(numr.group('digt'), FIX_FP_TABLE, ' ')
515
989
if numr.group('sign') is not None:
516
_val_str = numr.group('sign')+_val_str
990
value = numr.group('sign') + value
518
elif input.group('cplx') is not None:
519
real = Card._number_NFSC_RE.match(input.group('real'))
520
_realStr = translate(real.group('digt'), FIX_FP_TABLE, ' ')
992
elif m.group('cplx') is not None:
993
real = self._number_NFSC_RE.match(m.group('real'))
994
rdigt = translate(real.group('digt'), FIX_FP_TABLE, ' ')
521
995
if real.group('sign') is not None:
522
_realStr = real.group('sign')+_realStr
996
rdigt = real.group('sign') + rdigt
524
imag = Card._number_NFSC_RE.match(input.group('imag'))
525
_imagStr = translate(imag.group('digt'), FIX_FP_TABLE, ' ')
998
imag = self._number_NFSC_RE.match(m.group('imag'))
999
idigt = translate(imag.group('digt'), FIX_FP_TABLE, ' ')
526
1000
if imag.group('sign') is not None:
527
_imagStr = imag.group('sign') + _imagStr
528
_val_str = '(%s, %s)' % (_realStr, _imagStr)
530
self._valuestring = _val_str
531
self._update_cardimage()
533
def _locate_eq(self):
535
Locate the equal sign in the card image before column 10 and
536
return its location. It returns `None` if equal sign is not
537
present, or it is a commentary card.
540
# no equal sign for commentary cards (i.e. part of the string value)
541
_key = self.cardimage[:8].strip().upper()
542
if _key in Card._commentary_keys:
545
if _key == 'HIERARCH':
550
eq_loc = self.cardimage[:_limit].index("=")
555
def _get_key_string(self):
557
Locate the equal sign in the card image and return the string
558
before the equal sign. If there is no equal sign, return the
559
string before column 9.
562
eq_loc = self._locate_eq()
566
if self.cardimage[:8].upper() == 'HIERARCH':
568
self.__class__ = _HierarchCard
569
return self.cardimage[start:eq_loc]
571
def _get_value_comment_string(self):
573
Locate the equal sign in the card image and return the string
574
after the equal sign. If there is no equal sign, return the
575
string after column 8.
578
eq_loc = self._locate_eq()
581
return self.cardimage[eq_loc+1:]
583
def _check(self, option='ignore'):
584
"""Verify the card image with the specified option."""
590
if option == 'ignore':
592
elif option == 'parse':
594
# check the value only, no need to check key and comment for 'parse'
595
result = Card._value_NFSC_RE.match(self._get_value_comment_string())
597
# if not parsable (i.e. everything else) result = None
601
# verify the equal sign position
602
if self.key not in Card._commentary_keys and \
603
self.cardimage.find('=') != 8:
604
if option in ['exception', 'warn']:
606
'Card image is not FITS standard (equal sign not at ' \
608
raise ValueError(self._err_text + '\n%s' % self.cardimage)
609
elif option in ['fix', 'silentfix']:
610
result = self._check('parse')
611
self._fix_value(result)
614
'Fixed card to be FITS standard.: %s' % self.key
616
# verify the key, it is never fixable
617
# always fix silently the case where "=" is before column 9,
618
# since there is no way to communicate back to the _keys.
619
self._check_key(self.key)
621
# verify the value, it may be fixable
622
value = self._get_value_comment_string()
623
result = Card._value_FSC_RE.match(value)
624
if result is not None or self.key in Card._commentary_keys:
627
if option in ['fix', 'silentfix']:
628
result = self._check('parse')
629
self._fix_value(result)
632
'Fixed card to be FITS standard.: %s' % self.key
635
'Card image is not FITS standard (unparsable value ' \
636
'string: %s).' % value
637
raise ValueError(self._err_text + '\n%s' % self.cardimage)
639
# verify the comment (string), it is never fixable
640
if result is not None:
641
comm = result.group('comm')
643
self._check_text(comm)
646
return len(self.cardimage) // Card.length
648
def _verify(self, option='warn'):
649
"""Card class verification method."""
655
# Trapping the ValueError raised by _check method. Want execution to continue while printing
658
err.append(self.run_option(option, err_text=self._err_text,
659
fix_text=self._fix_text, fixable=self._fixable))
664
class RecordValuedKeywordCard(Card):
666
Class to manage record-valued keyword cards as described in the
667
FITS WCS Paper IV proposal for representing a more general
670
Record-valued keyword cards are string-valued cards where the
671
string is interpreted as a definition giving a record field name,
672
and its floating point value. In a FITS header they have the
675
keyword = 'field-specifier: float'
677
where `keyword` is a standard eight-character FITS keyword name,
678
`float` is the standard FITS ASCII representation of a floating
679
point number, and these are separated by a colon followed by a
680
single blank. The grammar for field-specifier is::
684
field-specifier.field
690
where `identifier` is a sequence of letters (upper or lower case),
691
underscores, and digits of which the first character must not be a
692
digit, and `index` is a sequence of digits. No blank characters
693
may occur in the field-specifier. The `index` is provided
694
primarily for defining array elements though it need not be used
697
Multiple record-valued keywords of the same name but differing
698
values may be present in a FITS header. The field-specifier may
699
be viewed as part of the keyword name.
701
Some examples follow::
707
DP1 = 'AUX.1.COEFF.0: 0'
708
DP1 = 'AUX.1.POWER.0: 1'
709
DP1 = 'AUX.1.COEFF.1: 0.00048828125'
710
DP1 = 'AUX.1.POWER.1: 1'
714
# A group of class level regular expression definitions that allow the
715
# extraction of the key, field-specifier, value, and comment from a
718
identifier = r'[a-zA-Z_]\w*'
719
field = identifier + r'(\.\d+)?'
720
field_specifier_s = r'%s(\.%s)*' % (field, field)
721
field_specifier_val = r'(?P<keyword>%s): (?P<val>%s\s*)' \
722
% (field_specifier_s, Card._numr_FSC)
723
field_specifier_NFSC_val = r'(?P<keyword>%s): (?P<val>%s\s*)' \
724
% (field_specifier_s, Card._numr_NFSC)
725
keyword_val = r'\'%s\'' % field_specifier_val
726
keyword_NFSC_val = r'\'%s\'' % field_specifier_NFSC_val
727
keyword_val_comm = r' +%s *(/ *(?P<comm>[ -~]*))?$' % keyword_val
728
keyword_NFSC_val_comm = r' +%s *(/ *(?P<comm>[ -~]*))?$' % keyword_NFSC_val
730
# regular expression to extract the field specifier and value from
731
# a card image (ex. 'AXIS.1: 2'), the value may not be FITS Standard
734
field_specifier_NFSC_image_RE = re.compile(field_specifier_NFSC_val)
736
# regular expression to extract the field specifier and value from
737
# a card value; the value may not be FITS Standard Compliant
738
# (ex. 'AXIS.1: 2.0e5')
740
field_specifier_NFSC_val_RE = re.compile(field_specifier_NFSC_val + r'$')
742
# regular expression to extract the key and the field specifier from a
743
# string that is being used to index into a card list that contains
744
# record value keyword cards (ex. 'DP1.AXIS.1')
746
keyword_name_RE = re.compile(r'(?P<key>%s)\.(?P<field_spec>%s)$'
747
% (identifier, field_specifier_s))
749
# regular expression to extract the field specifier and value and comment
750
# from the string value of a record value keyword card
751
# (ex "'AXIS.1: 1' / a comment")
753
keyword_val_comm_RE = re.compile(keyword_val_comm)
755
# regular expression to extract the field specifier and value and comment
756
# from the string value of a record value keyword card that is not FITS
757
# Standard Complient (ex "'AXIS.1: 1.0d12' / a comment")
759
keyword_NFSC_val_comm_RE = re.compile(keyword_NFSC_val_comm)
761
def __init__(self, key='', value='', comment=''):
766
The key, either the simple key or one that contains
769
value : str, optional
770
The value, either a simple value or one that contains a
773
comment : str, optional
777
mo = self.keyword_name_RE.match(key)
780
self._field_specifier = mo.group('field_spec')
781
key = mo.group('key')
783
if isinstance(value, str):
785
mo = self.field_specifier_NFSC_val_RE.match(value)
788
self._field_specifier = mo.group('keyword')
789
value = mo.group('val')
790
# The value should be a float, though we don't coerce
791
# ints into floats. Anything else should be a value
800
"Record-valued keyword card value must be "
801
"a floating point or integer value.")
804
"Value %s must be in the form "
805
"field_specifier: value (ex. 'NAXIS: 2')" % value)
807
raise ValueError('value %s is not a string' % value)
809
super(RecordValuedKeywordCard, self).__init__(key, value, comment)
812
def field_specifier(self):
813
self._extract_value()
814
return self._field_specifier
819
Return this card as a normal Card object not parsed as a record-valued
820
keyword card. Note that this returns a copy, so that modifications to
821
it do not update the original record-valued keyword card.
824
key = super(RecordValuedKeywordCard, self)._getkey()
825
return Card(key, self.strvalue(), self.comment)
828
key = super(RecordValuedKeywordCard, self)._getkey()
829
if not hasattr(self, '_field_specifier'):
831
return '%s.%s' % (key, self._field_specifier)
833
key = property(_getkey, Card.key.fset, doc=Card.key.__doc__)
836
"""The RVKC value should always be returned as a float."""
838
return float(super(RecordValuedKeywordCard, self)._getvalue())
840
def _setvalue(self, val):
841
if not isinstance(val, float):
848
raise ValueError('value %s is not a float' % val)
849
super(RecordValuedKeywordCard, self)._setvalue(val)
851
value = property(_getvalue, _setvalue, doc=Card.value.__doc__)
854
# class method definitins
858
def coerce(cls, card):
860
Coerces an input `Card` object to a `RecordValuedKeywordCard`
861
object if the value of the card meets the requirements of this
867
A `Card` object to coerce
872
- If the input card is coercible:
874
a new `RecordValuedKeywordCard` constructed from the
875
`key`, `value`, and `comment` of the input card.
877
- If the input card is not coercible:
882
mo = cls.field_specifier_NFSC_val_RE.match(card.value)
884
return cls(card.key, card.value, card.comment)
889
def upper_key(cls, key):
891
`classmethod` to convert a keyword value that may contain a
892
field-specifier to uppercase. The effect is to raise the
893
key to uppercase and leave the field specifier in its original
899
A keyword value that could be an integer, a key, or a
900
`key.field-specifier` value
905
the original integer key
914
mo = cls.keyword_name_RE.match(key)
917
return mo.group('key').strip().upper() + '.' + \
918
mo.group('field_spec')
920
return key.strip().upper()
921
# For API backwards-compatibility
923
deprecated(name='upperKey', alternative='upper_key()')(upper_key)
926
def valid_key_value(cls, key, value=0):
928
Determine if the input key and value can be used to form a
929
valid `RecordValuedKeywordCard` object. The `key` parameter
930
may contain the key only or both the key and field-specifier.
931
The `value` may be the value only or the field-specifier and
932
the value together. The `value` parameter is optional, in
933
which case the `key` parameter must contain both the key and
941
value : str or float-like, optional
946
valid input : A list containing the key, field-specifier, value
948
invalid input : An empty list
953
>>> valid_key_value('DP1','AXIS.1: 2')
954
>>> valid_key_value('DP1.AXIS.1', 2)
955
>>> valid_key_value('DP1.AXIS.1')
958
rtnKey = rtnFieldSpec = rtnValue = ''
959
myKey = cls.upper_key(key)
961
if isinstance(myKey, basestring):
962
validKey = cls.keyword_name_RE.match(myKey)
966
rtnValue = float(value)
970
rtnKey = validKey.group('key')
971
rtnFieldSpec = validKey.group('field_spec')
973
if isinstance(value, str) and \
974
Card._keywd_FSC_RE.match(myKey) and len(myKey) < 9:
975
validValue = cls.field_specifier_NFSC_val_RE.match(value)
977
rtnFieldSpec = validValue.group('keyword')
978
rtnValue = validValue.group('val')
982
return [rtnKey, rtnFieldSpec, rtnValue]
985
# For API backwards-compatibility
987
deprecated(name='validKeyValue',
988
alternative='valid_key_value()')(valid_key_value)
991
def create(cls, key='', value='', comment=''):
993
Create a card given the input `key`, `value`, and `comment`.
994
If the input key and value qualify for a
995
`RecordValuedKeywordCard` then that is the object created.
996
Otherwise, a standard `Card` object is created.
1003
value : str, optional
1006
comment : str, optional
1012
Either a `RecordValuedKeywordCard` or a `Card` object.
1015
if not cls.valid_key_value(key, value):
1016
# This should be just a normal card
1019
return cls(key, value, comment)
1020
# For API backwards-compatibility
1021
createCard = deprecated(name='createCard', alternative='create()')(create)
1024
def fromstring(cls, input):
1026
Create a card given the `input` string. If the `input` string
1027
can be parsed into a key and value that qualify for a
1028
`RecordValuedKeywordCard` then that is the object created.
1029
Otherwise, a standard `Card` object is created.
1034
The string representing the card
1039
either a `RecordValuedKeywordCard` or a `Card` object
1042
idx1 = input.find("'") + 1
1043
idx2 = input.rfind("'")
1045
if idx2 > idx1 and idx1 >= 0 and \
1046
cls.valid_key_value('', value=input[idx1:idx2]):
1047
# This calls Card.fromstring, but with the RecordValuedKeywordClass
1048
# as the cls argument (causing an RVKC to be created)
1049
return super(RecordValuedKeywordCard, cls).fromstring(input)
1051
# This calls Card.fromstring directly, creating a plain Card
1053
return Card.fromstring(input)
1055
# For API backwards-compatibility
1056
createCardFromString = deprecated(name='createCardFromString',
1057
alternative='fromstring()')(fromstring)
1059
def _update_cardimage(self):
1061
Generate a (new) card image from the attributes: `key`, `value`,
1062
`field_specifier`, and `comment`. Core code for `ascardimage`.
1065
super(RecordValuedKeywordCard, self)._update_cardimage()
1066
eqloc = self._cardimage.index('=')
1067
slashloc = self._cardimage.find('/')
1069
if hasattr(self, '_value_modified') and self._value_modified:
1070
# Bypass the automatic coertion to float here, so that values like
1071
# '2' will still be rendered as '2' instead of '2.0'
1072
value = super(RecordValuedKeywordCard, self).value
1073
val_str = _value_to_string(value).strip()
1075
val_str = self._valuestring
1077
val_str = "'%s: %s'" % (self._field_specifier, val_str)
1078
val_str = '%-20s' % val_str
1080
output = self._cardimage[:eqloc+2] + val_str
1083
output = output + self._cardimage[slashloc-1:]
1085
if len(output) <= Card.length:
1001
idigt = imag.group('sign') + idigt
1002
value = '(%s, %s)' % (rdigt, idigt)
1003
self._valuestring = value
1004
# The value itself has not been modified, but its serialized
1005
# representation (as stored in self._valuestring) has been changed, so
1006
# still set this card as having been modified (see ticket #137)
1007
self._modified = True
1009
def _format_keyword(self):
1011
if self.field_specifier:
1012
return '%-8s' % self.keyword.split('.', 1)[0]
1013
elif self._hierarch:
1014
return 'HIERARCH %s ' % self.keyword
1016
return '%-8s' % self.keyword
1020
def _format_value(self):
1022
float_types = (float, np.floating, complex, np.complexfloating)
1024
# Force the value to be parsed out first
1026
# But work with the underlying raw value instead (to preserve
1027
# whitespace, for now...)
1030
if self.keyword in self._commentary_keywords:
1031
# The value of a commentary card must be just a raw unprocessed
1034
elif (self._valuestring and not self._valuemodified and
1035
isinstance(self.value, float_types)):
1036
# Keep the existing formatting for float/complex numbers
1037
value = '%20s' % self._valuestring
1038
elif self.field_specifier:
1039
value = _format_value(self._value).strip()
1040
value = "'%s: %s'" % (self.field_specifier, value)
1042
value = _format_value(value)
1044
# For HIERARCH cards the value should be shortened to conserve space
1045
if not self.field_specifier and len(self.keyword) > 8:
1046
value = value.strip()
1050
def _format_comment(self):
1051
if not self.comment:
1054
return ' / %s' % self._comment
1056
def _format_image(self):
1057
keyword = self._format_keyword()
1059
value = self._format_value()
1060
is_commentary = keyword.strip() in self._commentary_keywords
1064
comment = self._format_comment()
1071
# put all parts together
1072
output = ''.join([keyword, delimiter, value, comment])
1074
# For HIERARCH cards we can save a bit of space if necessary by
1075
# removing the space between the keyword and the equals sign; I'm
1076
# guessing this is part of the HIEARCH card specification
1077
keywordvalue_length = len(keyword) + len(delimiter) + len(value)
1078
if (keywordvalue_length > self.length and
1079
keyword.startswith('HIERARCH')):
1080
if (keywordvalue_length == self.length + 1 and keyword[-1] == ' '):
1081
output = ''.join([keyword[:-1], delimiter, value, comment])
1083
# I guess the HIERARCH card spec is incompatible with CONTINUE
1085
raise ValueError('The keyword %s with its value is too long' %
1088
if len(output) <= self.length:
1086
1089
output = '%-80s' % output
1088
self._cardimage = output
1091
def _extract_value(self):
1092
"""Extract the keyword value from the card image."""
1094
valu = self._check(option='parse')
1098
"Unparsable card, fix it first with .verify('fix').")
1100
self._field_specifier = valu.group('keyword')
1102
if not hasattr(self, '_valuestring'):
1103
self._valuestring = valu.group('val')
1104
if not hasattr(self, '_value_modified'):
1105
self._value_modified = False
1107
return _str_to_num(translate(valu.group('val'), FIX_FP_TABLE2, ' '))
1111
Method to extract the field specifier and value from the card
1112
image. This is what is reported to the user when requesting
1113
the value of the `Card` using either an integer index or the
1114
card key without any field specifier.
1117
mo = self.field_specifier_NFSC_image_RE.search(self.cardimage)
1118
return self.cardimage[mo.start():mo.end()]
1120
def _fix_value(self, input):
1121
"""Fix the card image for fixable non-standard compliance."""
1126
tmp = self._get_value_comment_string()
1129
slash_loc = tmp.index("/")
1131
slash_loc = len(tmp)
1133
self._err_text = 'Illegal value %s' % tmp[:slash_loc]
1134
self._fixable = False
1135
raise ValueError(self._err_text)
1137
self._valuestring = translate(input.group('val'), FIX_FP_TABLE,
1139
self._update_cardimage()
1141
def _format_key(self):
1142
if hasattr(self, '_key') or hasattr(self, '_cardimage'):
1143
return '%-8s' % super(RecordValuedKeywordCard, self).key
1147
def _check_key(self, key):
1149
Verify the keyword to be FITS standard and that it matches the
1150
standard for record-valued keyword cards.
1154
keyword, field_specifier = key.split('.', 1)
1156
keyword, field_specifier = key, None
1158
super(RecordValuedKeywordCard, self)._check_key(keyword)
1161
if not re.match(self.field_specifier_s, key):
1162
self._err_text = 'Illegal keyword name %s' % repr(key)
1163
# TODO: Maybe fix by treating as normal card and not RVKC?
1164
self._fixable = False
1165
raise ValueError(self._err_text)
1167
def _check(self, option='ignore'):
1168
"""Verify the card image with the specified `option`."""
1172
self._fixable = True
1174
if option == 'ignore':
1176
elif option == 'parse':
1177
return self.keyword_NFSC_val_comm_RE.match(
1178
self._get_value_comment_string())
1180
# verify the equal sign position
1181
if self.cardimage.find('=') != 8:
1182
if option in ['exception', 'warn']:
1184
'Card image is not FITS standard (equal sign not at ' \
1186
raise ValueError(self._err_text + '\n%s' % self.cardimage)
1187
elif option in ['fix', 'silentfix']:
1188
result = self._check('parse')
1189
self._fix_value(result)
1193
'Fixed card to be FITS standard. : %s' % self.key
1196
self._check_key(self.key)
1200
self.keyword_val_comm_RE.match(self._get_value_comment_string())
1202
if result is not None:
1205
if option in ['fix', 'silentfix']:
1206
result = self._check('parse')
1207
self._fix_value(result)
1211
'Fixed card to be FITS standard.: %s' % self.key
1214
'Card image is not FITS standard (unparsable value ' \
1216
raise ValueError(self._err_text + '\n%s' % self.cardimage)
1218
# verify the comment (string), it is never fixable
1219
if result is not None:
1220
_str = result.group('comm')
1221
if _str is not None:
1222
self._check_text(_str)
1225
class CardList(list):
1226
"""FITS header card list class."""
1228
def __init__(self, cards=[], keylist=None):
1230
Construct the `CardList` object from a list of `Card` objects.
1235
A list of `Card` objects.
1238
super(CardList, self).__init__(cards)
1240
# if the key list is not supplied (as in reading in the FITS file),
1241
# it will be constructed from the card list.
1245
if isinstance(c, RecordValuedKeywordCard):
1246
self._keys.append(c.raw.key)
1248
self._keys.append(c.key)
1250
self._keys = keylist
1252
# find out how many blank cards are *directly* before the END card
1257
def __contains__(self, key):
1258
return upper_key(key) in self._keys
1260
def __getitem__(self, key):
1261
"""Get a `Card` by indexing or by the keyword name."""
1263
if isinstance(key, slice):
1264
return CardList(super(CardList, self).__getitem__(key),
1266
elif isinstance(key, basestring) and self._has_filter_char(key):
1267
return self.filter_list(key)
1269
idx = self.index_of(key)
1270
return super(CardList, self).__getitem__(idx)
1272
def __setitem__(self, key, value):
1273
"""Set a `Card` by indexing or by the keyword name."""
1275
if isinstance(value, Card):
1276
idx = self.index_of(key)
1278
# only set if the value is different from the old one
1279
if str(self[idx]) != str(value):
1280
super(CardList, self).__setitem__(idx, value)
1281
if isinstance(value, RecordValuedKeywordCard):
1282
self._keys[idx] = value.raw.key.upper()
1284
self._keys[idx] = value.key.upper()
1288
raise ValueError('%s is not a Card' % str(value))
1290
def __delitem__(self, key):
1291
"""Delete a `Card` from the `CardList`."""
1293
if self._has_filter_char(key):
1294
cardlist = self.filter_list(key)
1296
if len(cardlist) == 0:
1297
raise KeyError("Keyword '%s' not found." % key)
1299
for card in cardlist:
1303
idx = self.index_of(key)
1304
super(CardList, self).__delitem__(idx)
1305
del self._keys[idx] # update the keylist
1309
def __getslice__(self, start, end):
1310
return self[slice(start, end)]
1313
"""Format a list of cards into a string."""
1315
return ''.join(map(str, self))
1318
"""Format a list of cards into a printable string."""
1319
return '\n'.join(map(str, self))
1321
def __deepcopy__(self, memo):
1322
dup = CardList([copy.deepcopy(c, memo) for c in self])
1323
memo[id(self)] = dup
1327
mod = self.__dict__.get('_mod', False)
1329
# See if any of the cards were directly modified
1332
self.__dict__['_mod'] = True
1336
def _set_mod(self, value):
1337
self.__dict__['_mod'] = value
1338
# If the card list is explicitly set as 'not modified' then make sure
1339
# the same applies to its underlying cards
1342
card._modified = False
1344
_mod = property(_get_mod, _set_mod,
1345
doc='has this card list been modified since last write')
1348
"""Make a (deep)copy of the `CardList`."""
1350
return CardList([create_card_from_string(str(c)) for c in self])
1354
Return a list of all keywords from the `CardList`.
1356
Keywords include ``field_specifier`` for
1357
`RecordValuedKeywordCard` objects.
1360
return [card.key for card in self]
1364
Return a list of the values of all cards in the `CardList`.
1366
For `RecordValuedKeywordCard` objects, the value returned is
1367
the floating point value, exclusive of the
1368
``field_specifier``.
1371
return [c.value for c in self]
1373
def insert(self, pos, card, useblanks=True):
1375
Insert a `Card` to the `CardList`.
1380
The position (index, keyword name will not be allowed) to
1381
insert. The new card will be inserted before it.
1383
card : `Card` object
1384
The card to be inserted.
1386
useblanks : bool, optional
1387
If `useblanks` is `True`, and if there are blank cards
1388
directly before ``END``, it will use this space first,
1389
instead of appending after these blank cards, so the total
1390
space will not increase. When `useblanks` is `False`, the
1391
card will be appended at the end, even if there are blank
1392
cards in front of ``END``.
1395
if isinstance(card, Card):
1396
super(CardList, self).insert(pos, card)
1397
if isinstance(card, RecordValuedKeywordCard):
1398
self._keys.insert(pos, card.raw.key.upper())
1400
self._keys.insert(pos, card.key.upper()) # update the keylist
1403
self._use_blanks(card._ncards())
1408
raise ValueError('%s is not a Card' % str(card))
1410
def append(self, card, useblanks=True, bottom=False):
1412
Append a `Card` to the `CardList`.
1416
card : `Card` object
1417
The `Card` to be appended.
1419
useblanks : bool, optional
1420
Use any *extra* blank cards?
1422
If `useblanks` is `True`, and if there are blank cards
1423
directly before ``END``, it will use this space first,
1424
instead of appending after these blank cards, so the total
1425
space will not increase. When `useblanks` is `False`, the
1426
card will be appended at the end, even if there are blank
1427
cards in front of ``END``.
1429
bottom : bool, optional
1430
If `False` the card will be appended after the last
1431
non-commentary card. If `True` the card will be appended
1432
after the last non-blank card.
1435
if isinstance(card, Card):
1436
nc = len(self) - self._blanks
1439
# locate last non-commentary card
1440
for idx in range(nc - 1, -1, -1):
1441
if self[idx].key not in Card._commentary_keys:
1444
super(CardList, self).insert(idx + 1, card)
1445
if isinstance(card, RecordValuedKeywordCard):
1446
self._keys.insert(idx + 1, card.raw.key.upper())
1448
self._keys.insert(idx + 1, card.key.upper())
1450
self._use_blanks(card._ncards())
1454
raise ValueError("%s is not a Card" % str(card))
1456
def index_of(self, key, backward=False):
1458
Get the index of a keyword in the `CardList`.
1463
The keyword name (a string) or the index (an integer).
1465
backward : bool, optional
1466
When `True`, search the index from the ``END``, i.e.,
1472
The index of the `Card` with the given keyword.
1477
elif isinstance(key, basestring):
1478
_key = key.strip().upper()
1479
if _key[:8] == 'HIERARCH':
1480
_key = _key[8:].strip()
1483
# We have to search backwards through they key list
1484
keys = reversed(keys)
1486
idx = keys.index(_key)
1488
reqkey = RecordValuedKeywordCard.valid_key_value(key)
1493
jdx = keys[idx:].index(reqkey[0].upper())
1496
if isinstance(card, RecordValuedKeywordCard) and \
1497
reqkey[1] == card.field_specifier:
1500
raise KeyError('Keyword %s not found.' % repr(key))
1504
raise KeyError('Keyword %s not found.' % repr(key))
1507
idx = len(keys) - idx - 1
1510
raise KeyError('Illegal key data type %s' % type(key))
1512
def filter_list(self, key):
1514
Construct a `CardList` that contains references to all of the cards in
1515
this `CardList` that match the input key value including any special
1516
filter keys (``*``, ``?``, and ``...``).
1521
key value to filter the list with
1526
A `CardList` object containing references to all the
1532
mykey = upper_key(key)
1533
re_str = mykey.replace('*', '\w*') + '$'
1534
re_str = re_str.replace('?', '\w')
1535
re_str = re_str.replace('...', '\S*')
1536
match_RE = re.compile(re_str)
1539
match_str = card.key
1541
if match_RE.match(match_str):
1545
filterList = filter_list # For API backwards-compatibility
1547
def count_blanks(self):
1549
Returns how many blank cards are *directly* before the ``END``
1553
blank = ' ' * Card.length
1554
for idx in range(1, len(self)):
1555
if str(self[-idx]) != blank:
1556
self._blanks = idx - 1
1559
def _has_filter_char(self, key):
1561
Return `True` if the input key contains one of the special filtering
1562
characters (``*``, ``?``, or ...).
1565
if isinstance(key, basestring) and \
1566
(key.endswith('...') or key.find('*') > 0 or key.find('?') > 0):
1571
def _pos_insert(self, card, before, after, useblanks=True, replace=False):
1573
Insert a `Card` to the location specified by before or after.
1575
The argument `before` takes precedence over `after` if both
1576
specified. They can be either a keyword name or index.
1580
insertionkey = after
1582
insertionkey = before
1584
def get_insertion_idx():
1585
if not (isinstance(insertionkey, int) and
1586
insertionkey >= len(self)):
1587
idx = self.index_of(insertionkey)
1597
old_idx = self.index_of(card.key)
1598
insertion_idx = get_insertion_idx()
1600
if insertion_idx >= len(self) and old_idx == len(self) - 1:
1603
if before is not None:
1604
if old_idx == insertion_idx - 1:
1606
elif after is not None and old_idx == insertion_idx:
1611
idx = get_insertion_idx()
1612
self.insert(idx, card, useblanks=useblanks)
1614
def _use_blanks(self, how_many):
1615
if self._blanks > 0:
1616
for idx in range(min(self._blanks, how_many)):
1617
del self[-1] # it also delete the keylist item
1620
def create_card(key='', value='', comment=''):
1621
return RecordValuedKeywordCard.create(key, value, comment)
1622
create_card.__doc__ = RecordValuedKeywordCard.create.__doc__
1623
# For API backwards-compatibility
1624
createCard = deprecated(name='createCard',
1625
alternative='create_card()')(create_card)
1628
def create_card_from_string(input):
1629
return RecordValuedKeywordCard.fromstring(input)
1630
create_card_from_string.__doc__ = RecordValuedKeywordCard.fromstring.__doc__
1631
# For API backwards-compat
1632
createCardFromString = \
1633
deprecated(name='createCardFromString',
1634
alternative='fromstring()')(create_card_from_string)
1637
return RecordValuedKeywordCard.upper_key(key)
1638
upper_key.__doc__ = RecordValuedKeywordCard.upper_key.__doc__
1639
# For API backwards-compat
1640
upperKey = deprecated(name='upperKey', alternative='upper_key()')(upper_key)
1643
class _HierarchCard(Card):
1645
Cards begins with ``HIERARCH`` which allows keyword name longer than 8
1650
"""Returns the keyword name parsed from the card image."""
1652
if not hasattr(self, '_key'):
1653
head = self._get_key_string()
1654
self._key = head.strip()
1657
def _format_key(self):
1658
if hasattr(self, '_key') or hasattr(self, '_cardimage'):
1659
return 'HIERARCH %s ' % self.key
1663
def _format_value(self):
1664
val_str = super(_HierarchCard, self)._format_value()
1665
# conserve space for HIERARCH cards
1666
return val_str.strip()
1669
def _verify(self, option='warn'):
1670
"""No verification (for now)."""
1675
class _ContinueCard(Card):
1677
Cards having more than one 80-char "physical" cards, the cards after
1678
the first one must start with ``CONTINUE`` and the whole card must have
1683
"""Format a list of cards into a printable string."""
1686
for idx in range(len(self.cardimage) // 80):
1687
output.append(self.cardimage[idx*80:(idx+1)*80])
1688
return '\n'.join(output)
1690
def _iter_cards(self):
1691
ncards = self._ncards()
1692
for idx in range(ncards):
1693
# take each 80-char card as a regular card and use its methods.
1694
card = Card.fromstring(self.cardimage[idx*80:(idx+1)*80])
1695
if idx > 0 and card.key != 'CONTINUE':
1696
raise ValueError('Long card image must have CONTINUE cards '
1697
'after the first card.')
1698
if not isinstance(card.value, str):
1700
'Cards with CONTINUE must have string value.')
1703
def _extract_value(self):
1704
"""Extract the keyword value from the card image."""
1707
for card in self._iter_cards():
1708
val = card.value.rstrip().replace("''", "'")
1709
# drop the ending "&"
1710
if val and val[-1] == '&':
1713
return ''.join(output).rstrip()
1715
def _extract_comment(self):
1716
"""Extract the comment from the card image."""
1719
for card in self._iter_cards():
1721
if isinstance(comm, str) and comm != '':
1722
output.append(comm.rstrip() + ' ')
1723
return ''.join(output).rstrip()
1725
def _breakup_strings(self):
1091
# longstring case (CONTINUE card)
1092
# try not to use CONTINUE if the string value can fit in one line.
1093
# Instead, just truncate the comment
1094
if (isinstance(self.value, str) and
1095
len(value) > (self.length - 10)):
1096
output = self._format_long_image()
1098
warnings.warn('Card is too long, comment will be truncated.',
1100
output = output[:Card.length]
1103
def _format_long_image(self):
1727
1105
Break up long string value/comment into ``CONTINUE`` cards.
1728
1106
This is a primitive implementation: it will put the value