~ubuntu-branches/ubuntu/wily/pyfits/wily-proposed

« back to all changes in this revision

Viewing changes to lib/pyfits/card.py

  • Committer: Package Import Robot
  • Author(s): Aurelien Jarno
  • Date: 2012-12-30 15:03:07 UTC
  • mfrom: (1.1.9)
  • Revision ID: package-import@ubuntu.com-20121230150307-ltycp0zrtyh53gd8
Tags: 1:3.1-1
* New upstream version.
  - Refreshed 01-zlib.diff.
  - Added 02-disable-version-setup-hook.diff to prevent version.py to
    be regenerated with a timestamp at clean time.
* Compress source and .deb with xz.
* Bump Standards-Version to 3.9.4.0 (no changes).

Show diffs side-by-side

added added

removed removed

Lines of Context:
6
6
 
7
7
import numpy as np
8
8
 
9
 
from pyfits.util import _str_to_num, _is_int, deprecated, maketrans, translate
10
 
from pyfits.verify import _Verify, _ErrList, VerifyError
11
 
 
12
 
 
13
 
__all__ = ['Card', 'CardList', 'RecordValuedKeywordCard', 'create_card',
14
 
           'create_card_from_string', 'upper_key', 'createCard',
15
 
           'createCardFromString', 'upperKey', 'Undefined']
 
9
import pyfits
 
10
from pyfits.util import (_str_to_num, _is_int, deprecated, maketrans,
 
11
                         translate, _words_group, lazyproperty)
 
12
from pyfits.verify import _Verify, _ErrList, VerifyError, VerifyWarning
 
13
 
 
14
 
 
15
__all__ = ['Card', 'CardList', 'create_card', 'create_card_from_string',
 
16
           'upper_key', 'createCard', 'createCardFromString', 'upperKey',
 
17
           'Undefined']
16
18
 
17
19
 
18
20
FIX_FP_TABLE = maketrans('de', 'DE')
19
21
FIX_FP_TABLE2 = maketrans('dD', 'eE')
20
22
 
21
23
 
 
24
BLANK_CARD = ' ' * 80
 
25
 
 
26
 
22
27
class Undefined:
23
28
    """Undefined value."""
24
29
 
28
33
UNDEFINED = Undefined()
29
34
 
30
35
 
 
36
class CardList(list):
 
37
    """
 
38
    .. deprecated:: 3.1
 
39
        `CardList` used to provide the list-like functionality for manipulating
 
40
        a header as a list of cards.  This functionality is now subsumed into
 
41
        the `Header` class itself, so it is no longer necessary to create or
 
42
        use `CardList`\s.
 
43
    """
 
44
 
 
45
    def __init__(self, cards=[], keylist=None):
 
46
        """
 
47
        Construct the `CardList` object from a list of `Card` objects.
 
48
 
 
49
        `CardList` is now merely a thin wrapper around `Header` to provide
 
50
        backwards compatibility for the old API.  This should not be used for
 
51
        any new code.
 
52
 
 
53
        Parameters
 
54
        ----------
 
55
        cards
 
56
            A list of `Card` objects.
 
57
        """
 
58
 
 
59
        warnings.warn(
 
60
                'The CardList class has been deprecated; all its former '
 
61
                'functionality has been subsumed by the Header class, so '
 
62
                'CardList objects should not be directly created.  See the '
 
63
                'PyFITS 3.1.0 CHANGELOG for more details.',
 
64
                DeprecationWarning)
 
65
 
 
66
        # This is necessary for now to prevent a circular import
 
67
        from pyfits.header import Header
 
68
 
 
69
        # I'm not sure if they keylist argument here was ever really useful;
 
70
        # I'm going to just say don't use it.
 
71
        if keylist is not None:
 
72
            raise ValueError(
 
73
                'The keylist argument to CardList() is no longer supported.')
 
74
 
 
75
        if isinstance(cards, Header):
 
76
            self._header = cards
 
77
        else:
 
78
            self._header = Header(cards)
 
79
 
 
80
        super(CardList, self).__init__(self._header.cards)
 
81
 
 
82
    def __contains__(self, key):
 
83
        return key in self._header
 
84
 
 
85
    def __iter__(self):
 
86
        return iter(self._header.cards)
 
87
 
 
88
    def __getitem__(self, key):
 
89
        """Get a `Card` by indexing or by the keyword name."""
 
90
 
 
91
        if self._header._haswildcard(key):
 
92
            return [copy.copy(self._header._cards[idx])
 
93
                    for idx in self._header._wildcardmatch(key)]
 
94
        elif isinstance(key, slice):
 
95
            return CardList(self._header.cards[key])
 
96
 
 
97
        idx = self._header._cardindex(key)
 
98
        return self._header.cards[idx]
 
99
 
 
100
    def __setitem__(self, key, value):
 
101
        """Set a `Card` by indexing or by the keyword name."""
 
102
 
 
103
        if isinstance(value, tuple) and (1 < len(value) <= 3):
 
104
            value = Card(*value)
 
105
 
 
106
        if isinstance(value, Card):
 
107
            idx = self._header._cardindex(key)
 
108
            card = self._header.cards[idx]
 
109
            if str(card) != str(value):
 
110
                # Replace the existing card at this index by delete/insert
 
111
                del self._header[idx]
 
112
                self._header.insert(idx, value)
 
113
        else:
 
114
            raise ValueError('%s is not a Card' % str(value))
 
115
 
 
116
    def __delitem__(self, key):
 
117
        """Delete a `Card` from the `CardList`."""
 
118
 
 
119
        if key not in self._header._keyword_indices:
 
120
            raise KeyError("Keyword '%s' not found." % key)
 
121
        del self._header[key]
 
122
 
 
123
    def __getslice__(self, start, end):
 
124
        return CardList(self[slice(start, end)])
 
125
 
 
126
    def __repr__(self):
 
127
        """Format a list of cards into a string."""
 
128
 
 
129
        return str(self._header)
 
130
 
 
131
    def __str__(self):
 
132
        """Format a list of cards into a printable string."""
 
133
 
 
134
        return '\n'.join(str(card) for card in self)
 
135
 
 
136
    @deprecated('3.1', alternative=':meth:`Header.copy`', pending=False)
 
137
    def copy(self):
 
138
        """Make a (deep)copy of the `CardList`."""
 
139
 
 
140
        return CardList(self._header.copy())
 
141
 
 
142
    @deprecated('3.1', alternative=':meth:`Header.keys`', pending=False)
 
143
    def keys(self):
 
144
        """
 
145
        Return a list of all keywords from the `CardList`.
 
146
        """
 
147
 
 
148
        return self._header.keys()
 
149
 
 
150
    @deprecated('3.1', alternative=':meth:`Header.values`', pending=False)
 
151
    def values(self):
 
152
        """
 
153
        Return a list of the values of all cards in the `CardList`.
 
154
 
 
155
        For `RecordValuedKeywordCard` objects, the value returned is
 
156
        the floating point value, exclusive of the
 
157
        ``field_specifier``.
 
158
        """
 
159
 
 
160
        return self._header.values()
 
161
 
 
162
    @deprecated('3.1', alternative=':meth:`Header.append`', pending=False)
 
163
    def append(self, card, useblanks=True, bottom=False):
 
164
        """
 
165
        Append a `Card` to the `CardList`.
 
166
 
 
167
        Parameters
 
168
        ----------
 
169
        card : `Card` object
 
170
            The `Card` to be appended.
 
171
 
 
172
        useblanks : bool, optional
 
173
            Use any *extra* blank cards?
 
174
 
 
175
            If `useblanks` is `True`, and if there are blank cards
 
176
            directly before ``END``, it will use this space first,
 
177
            instead of appending after these blank cards, so the total
 
178
            space will not increase.  When `useblanks` is `False`, the
 
179
            card will be appended at the end, even if there are blank
 
180
            cards in front of ``END``.
 
181
 
 
182
        bottom : bool, optional
 
183
           If `False` the card will be appended after the last
 
184
           non-commentary card.  If `True` the card will be appended
 
185
           after the last non-blank card.
 
186
        """
 
187
 
 
188
        self._header.append(card, useblanks=useblanks, bottom=bottom)
 
189
 
 
190
    @deprecated('3.1', alternative=':meth:`Header.extend`', pending=False)
 
191
    def extend(self, cards):
 
192
        self._header.extend(cards)
 
193
 
 
194
    @deprecated('3.1', alternative=':meth:`Header.insert`', pending=False)
 
195
    def insert(self, idx, card, useblanks=True):
 
196
        """
 
197
        Insert a `Card` to the `CardList`.
 
198
 
 
199
        Parameters
 
200
        ----------
 
201
        pos : int
 
202
            The position (index, keyword name will not be allowed) to
 
203
            insert. The new card will be inserted before it.
 
204
 
 
205
        card : `Card` object
 
206
            The card to be inserted.
 
207
 
 
208
        useblanks : bool, optional
 
209
            If `useblanks` is `True`, and if there are blank cards
 
210
            directly before ``END``, it will use this space first,
 
211
            instead of appending after these blank cards, so the total
 
212
            space will not increase.  When `useblanks` is `False`, the
 
213
            card will be appended at the end, even if there are blank
 
214
            cards in front of ``END``.
 
215
        """
 
216
 
 
217
        self._header.insert(idx, card, useblanks=useblanks)
 
218
 
 
219
    @deprecated('3.1', alternative=':meth:`Header.remove`')
 
220
    def remove(self, card):
 
221
        del self._header[self.index(card)]
 
222
 
 
223
    @deprecated('3.1', alternative=':meth:`Header.pop`')
 
224
    def pop(self, index=-1):
 
225
        return self._header.pop(index)
 
226
 
 
227
    @deprecated('3.1', alternative=':meth:`Header.index`')
 
228
    def index(self, card):
 
229
        return self._header.cards.index(card)
 
230
 
 
231
    @deprecated('3.1', alternative=':meth:`Header.count`')
 
232
    def count(self, card):
 
233
        return self._header.cards.count(card)
 
234
 
 
235
    @deprecated('3.1', alternative=':meth:`Header.index`', pending=False)
 
236
    def index_of(self, key, backward=False):
 
237
        """
 
238
        Get the index of a keyword in the `CardList`.
 
239
 
 
240
        Parameters
 
241
        ----------
 
242
        key : str or int
 
243
            The keyword name (a string) or the index (an integer).
 
244
 
 
245
        backward : bool, (optional)
 
246
            When `True`, search the index from the ``END``, i.e.,
 
247
            backward.
 
248
 
 
249
        Returns
 
250
        -------
 
251
        index : int
 
252
            The index of the `Card` with the given keyword.
 
253
        """
 
254
 
 
255
        # Backward is just ignored now, since the search is not linear anyways
 
256
 
 
257
        if _is_int(key) or isinstance(key, basestring):
 
258
            return self._header._cardindex(key)
 
259
        else:
 
260
            raise KeyError('Illegal key data type %s' % type(key))
 
261
 
 
262
    @deprecated('3.1', alternative='`header[<wildcard_pattern>]`')
 
263
    def filter_list(self, key):
 
264
        """
 
265
        Construct a `CardList` that contains references to all of the cards in
 
266
        this `CardList` that match the input key value including any special
 
267
        filter keys (``*``, ``?``, and ``...``).
 
268
 
 
269
        Parameters
 
270
        ----------
 
271
        key : str
 
272
            key value to filter the list with
 
273
 
 
274
        Returns
 
275
        -------
 
276
        cardlist :
 
277
            A `CardList` object containing references to all the
 
278
            requested cards.
 
279
        """
 
280
 
 
281
        return CardList(self._header[key])
 
282
 
 
283
    # For API backwards-compatibility
 
284
    @deprecated('3.0', alternative=':meth:`filter_list`', pending=False)
 
285
    def filterList(self, key):
 
286
        return self.filter_list(key)
 
287
 
 
288
    @deprecated('3.1', pending=False)
 
289
    def count_blanks(self):
 
290
        """
 
291
        Returns how many blank cards are *directly* before the ``END``
 
292
        card.
 
293
        """
 
294
 
 
295
        return self._header._countblanks()
 
296
 
 
297
 
31
298
class Card(_Verify):
32
 
 
33
 
    # string length of a card
34
299
    length = 80
35
300
 
36
301
    # String for a FITS standard compliant (FSC) keyword.
37
 
    _keywd_FSC = r'[A-Z0-9_-]* *$'
38
 
    _keywd_FSC_RE = re.compile(_keywd_FSC)
 
302
    _keywd_FSC_RE = re.compile(r'^[A-Z0-9_-]{0,8}$')
 
303
    # This will match any printable ASCII character excluding '='
 
304
    _keywd_hierarch_RE = re.compile(r'^(?:HIERARCH )?(?:^[ -<>-~]+ ?)+$', re.I)
39
305
 
40
306
    # A number sub-string, either an integer or a float in fixed or
41
307
    # scientific notation.  One for FSC and one for non-FSC (NFSC) format:
47
313
    _numr_NFSC = r'[+-]? *' + _digits_NFSC
48
314
 
49
315
    # This regex helps delete leading zeros from numbers, otherwise
50
 
    # Python might evaluate them as octal values.
51
 
    _number_FSC_RE = re.compile(r'(?P<sign>[+-])?0*(?P<digt>%s)'
 
316
    # Python might evaluate them as octal values (this is not-greedy, however,
 
317
    # so it may not strip leading zeros from a float, which is fine)
 
318
    _number_FSC_RE = re.compile(r'(?P<sign>[+-])?0*?(?P<digt>%s)'
52
319
                                % _digits_FSC)
53
 
    _number_NFSC_RE = re.compile(r'(?P<sign>[+-])? *0*(?P<digt>%s)'
 
320
    _number_NFSC_RE = re.compile(r'(?P<sign>[+-])? *0*?(?P<digt>%s)'
54
321
                                 % _digits_NFSC)
55
322
 
56
323
    # FSC commentary card string which must contain printable ASCII characters.
97
364
    _value_NFSC_RE = re.compile(
98
365
        r'(?P<valu_field> *'
99
366
            r'(?P<valu>'
100
 
                r'\'(?P<strg>([ -~]+?|\'\'|)) *?\'(?=$|/| )|'
 
367
                r'\'(?P<strg>([ -~]+?|\'\'|) *?)\'(?=$|/| )|'
101
368
                r'(?P<bool>[FT])|'
102
369
                r'(?P<numr>' + _numr_NFSC + r')|'
103
370
                r'(?P<cplx>\( *'
109
376
            r'(?P<comm>(.|\n)*)'
110
377
        r')?$')
111
378
 
112
 
    # keys of commentary cards
113
 
    _commentary_keys = ['', 'COMMENT', 'HISTORY']
114
 
 
115
 
    def __new__(cls, key='', value='', comment=''):
116
 
        """
117
 
        Return the appropriate Card subclass depending on they key and/or
118
 
        value.
119
 
        """
120
 
 
121
 
        klass = cls
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)
128
 
 
129
 
    def __init__(self, key='', value='', comment=''):
130
 
        """
131
 
        Construct a card from `key`, `value`, and (optionally) `comment`.
132
 
        Any specifed arguments, except defaults, must be compliant to FITS
133
 
        standard.
134
 
 
135
 
        Parameters
136
 
        ----------
137
 
        key : str, optional
138
 
            keyword name
139
 
 
140
 
        value : str, optional
141
 
            keyword value
142
 
 
143
 
        comment : str, optional
144
 
            comment
145
 
        """
146
 
 
147
 
        if key != '' or value != '' or comment != '':
148
 
            self.key = key
149
 
            self._update_value(value)
150
 
            self._update_comment(comment)
151
 
            # for commentary cards, value can only be strings and there
152
 
            # is no comment
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 '
156
 
                                     'string.')
157
 
        else:
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>[ -~]*))?$' %
 
386
                              _rvkc_keyword_val)
 
387
 
 
388
    _rvkc_field_specifier_val_RE = re.compile(_rvkc_field_specifier_val)
 
389
 
 
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)))
 
396
 
 
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)
 
401
 
 
402
    _commentary_keywords = ['', 'COMMENT', 'HISTORY', 'END']
 
403
 
 
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']
 
408
 
 
409
        self._keyword = None
 
410
        self._value = None
 
411
        self._comment = None
 
412
        self._image = None
 
413
 
 
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
 
416
        # point
 
417
        self._verified = True
 
418
 
 
419
        # A flag to conveniently mark whether or not this was a valid HIERARCH
 
420
        # card
 
421
        self._hierarch = False
 
422
 
 
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
 
426
 
 
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
 
430
            # value
 
431
            if keyword is not None:
 
432
                self.keyword = keyword
 
433
            if value is not None:
 
434
                self.value = value
 
435
 
 
436
        if comment is not None:
 
437
            self.comment = comment
 
438
 
159
439
        self._modified = False
 
440
        self._valuestring = None
 
441
        self._valuemodified = False
 
442
 
 
443
    def __repr__(self):
 
444
        return repr((self.keyword, self.value, self.comment))
160
445
 
161
446
    def __str__(self):
162
 
        return self.cardimage
163
 
 
164
 
    def _getkey(self):
 
447
        return self.image
 
448
 
 
449
    def __len__(self):
 
450
        return 3
 
451
 
 
452
    def __getitem__(self, index):
 
453
        return (self.keyword, self.value, self.comment)[index]
 
454
 
 
455
    @property
 
456
    def keyword(self):
165
457
        """Returns the keyword name parsed from the card image."""
166
 
 
167
 
        if not hasattr(self, '_key'):
168
 
            head = self._get_key_string()
169
 
            self._key = head.strip().upper()
170
 
        return self._key
171
 
 
172
 
    def _setkey(self, val):
 
458
        if self._keyword is not None:
 
459
            return self._keyword
 
460
        elif self._image:
 
461
            self._keyword = self._parse_keyword()
 
462
            return self._keyword
 
463
        else:
 
464
            self.keyword = ''
 
465
            return ''
 
466
 
 
467
    @keyword.setter
 
468
    def keyword(self, keyword):
173
469
        """Set the key attribute; once set it cannot be modified."""
174
 
 
175
 
        if hasattr(self, '_key'):
176
 
            raise AttributeError('Keyword name cannot be modified.')
177
 
 
178
 
        if isinstance(val, basestring):
179
 
            val = val.strip()
180
 
            if len(val) <= 8:
181
 
                val = val.upper()
182
 
                if val == 'END':
 
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.")
184
 
                self._check_key(val)
 
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
 
493
 
 
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:]
 
498
                else:
 
499
                    # We'll gladly create a HIERARCH card, but a warning is
 
500
                    # also displayed
 
501
                    warnings.warn(
 
502
                        'Keyword name %r is greater than 8 characters or '
 
503
                        'or contains spaces; a HIERARCH card will be created.' %
 
504
                        keyword, VerifyWarning)
185
505
            else:
186
 
                if val[:8].upper() == 'HIERARCH':
187
 
                    val = val[8:].strip()
188
 
                    self.__class__ = _HierarchCard
189
 
                else:
190
 
                    raise ValueError('Keyword name %s is too long (> 8), use HIERARCH.'
191
 
                                     % val)
192
 
        else:
193
 
            raise ValueError('Keyword name %s is not a string.' % repr(val))
194
 
        self._key = val
195
 
        self._modified = True
196
 
 
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')
200
 
 
201
 
    def _getvalue(self):
202
 
        """Get the value attribute from the card image if not already set."""
203
 
 
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()
208
 
        return self._value
209
 
 
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
214
 
            # it
215
 
            self.key
216
 
            self.comment
217
 
            del self._cardimage
218
 
 
219
 
    value = property(_getvalue, _setvalue, doc='Card value')
220
 
 
221
 
    def _update_value(self, val):
222
 
        """Set the value attribute."""
223
 
 
224
 
        if isinstance(val, (str, int, long, float, complex, bool, Undefined,
225
 
                            np.floating, np.integer, np.complexfloating,
226
 
                            np.bool_)):
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
232
 
            self._value = val
233
 
        else:
234
 
            raise ValueError('Illegal value %s.' % repr(val))
235
 
 
236
 
    def _getcomment(self):
 
506
                raise ValueError('Illegal keyword name: %r.' % keyword)
 
507
            self._keyword = keyword
 
508
            self._modified = True
 
509
        else:
 
510
            raise ValueError('Keyword name %r is not a string.' % keyword)
 
511
 
 
512
    @property
 
513
    @deprecated('3.1', alternative='the `.keyword` attribute')
 
514
    def key(self):
 
515
        return self.keyword
 
516
 
 
517
    @property
 
518
    def value(self):
 
519
        if self.field_specifier:
 
520
            return float(self._value)
 
521
 
 
522
        if self._value is not None:
 
523
            value = self._value
 
524
        elif self._valuestring is not None or self._image:
 
525
            self._value = self._parse_value()
 
526
            value = self._value
 
527
        else:
 
528
            self._value = value = ''
 
529
 
 
530
        if pyfits.STRIP_HEADER_WHITESPACE and isinstance(value, basestring):
 
531
            value = value.rstrip()
 
532
 
 
533
        return value
 
534
 
 
535
    @value.setter
 
536
    def value(self, value):
 
537
        if self._invalid:
 
538
            raise ValueError(
 
539
                'The value of invalid/unparseable cards cannot set.  Either '
 
540
                'delete this card from the header or replace it.')
 
541
 
 
542
        if value is None:
 
543
            value = ''
 
544
        oldvalue = self._value
 
545
        if oldvalue is None:
 
546
            oldvalue = ''
 
547
 
 
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)
 
552
 
 
553
        if isinstance(value, unicode):
 
554
            try:
 
555
                # Any string value must be encodable as ASCII
 
556
                value.encode('ascii')
 
557
            except UnicodeEncodeError:
 
558
                raise ValueError(
 
559
                    'FITS header values must contain standard ASCII '
 
560
                    'characters; %r contains characters not representable in '
 
561
                    'ASCII.' % value)
 
562
 
 
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()
 
568
        else:
 
569
            different = oldvalue != value
 
570
 
 
571
        if different:
 
572
            self._value = value
 
573
            self._modified = True
 
574
            self._valuestring = None
 
575
            self._valuemodified = True
 
576
            if self.field_specifier:
 
577
                try:
 
578
                    self._value = _int_or_float(self._value)
 
579
                except ValueError:
 
580
                    raise ValueError('value %s is not a float' %
 
581
                                     self._value)
 
582
 
 
583
    @value.deleter
 
584
    def value(self):
 
585
        if self._invalid:
 
586
            raise ValueError(
 
587
                'The value of invalid/unparseable cards cannot deleted.  '
 
588
                'Either delete this card from the header or replace it.')
 
589
 
 
590
        if not self.field_specifier:
 
591
            self.value = ''
 
592
        else:
 
593
            raise AttributeError('Values cannot be deleted from record-valued '
 
594
                                 'keyword cards')
 
595
 
 
596
    @property
 
597
    def comment(self):
237
598
        """Get the comment attribute from the card image if not already set."""
238
599
 
239
 
        if not hasattr(self, '_comment'):
240
 
            self._comment = self._extract_comment()
241
 
        return self._comment
242
 
 
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
247
 
            # it
248
 
            self.key
249
 
            self.value
250
 
            del self._cardimage
251
 
 
252
 
    comment = property(_getcomment, _setcomment, doc='Card comment')
253
 
 
254
 
    def _update_comment(self, val):
255
 
        """Set the comment attribute."""
256
 
 
257
 
        if isinstance(val, basestring):
258
 
            self._check_text(val)
259
 
        else:
260
 
            if val is not None:
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
264
 
        self._comment = val
265
 
 
266
 
    @property
 
600
        if self._comment is not None:
 
601
            return self._comment
 
602
        elif self._image:
 
603
            self._comment = self._parse_comment()
 
604
            return self._comment
 
605
        else:
 
606
            self.comment = ''
 
607
            return ''
 
608
 
 
609
    @comment.setter
 
610
    def comment(self, comment):
 
611
        if self._invalid:
 
612
            raise ValueError(
 
613
                'The comment of invalid/unparseable cards cannot set.  Either '
 
614
                'delete this card from the header or replace it.')
 
615
 
 
616
        if comment is None:
 
617
            comment = ''
 
618
 
 
619
        if isinstance(comment, unicode):
 
620
            try:
 
621
                # Any string value must be encodable as ASCII
 
622
                comment.encode('ascii')
 
623
            except UnicodeEncodeError:
 
624
                raise ValueError(
 
625
                    'FITS header comments must contain standard ASCII '
 
626
                    'characters; %r contains characters not representable in '
 
627
                    'ASCII.' % comment)
 
628
 
 
629
        oldcomment = self._comment
 
630
        if oldcomment is None:
 
631
            oldcomment = ''
 
632
        if comment != oldcomment:
 
633
            self._comment = comment
 
634
            self._modified = True
 
635
 
 
636
    @comment.deleter
 
637
    def comment(self):
 
638
        if self._invalid:
 
639
            raise ValueError(
 
640
                'The comment of invalid/unparseable cards cannot deleted.  '
 
641
                'Either delete this card from the header or replace it.')
 
642
 
 
643
        self.comment = ''
 
644
 
 
645
    @property
 
646
    def field_specifier(self):
 
647
        """
 
648
        The field-specifier of record-valued keyword cards; always `None` on
 
649
        normal cards.
 
650
        """
 
651
 
 
652
        # Ensure that the keyword exists and has been parsed--the will set the
 
653
        # internal _field_specifier attribute if this is a RVKC.
 
654
        if self.keyword:
 
655
            return self._field_specifier
 
656
        else:
 
657
            return None
 
658
 
 
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
 
674
 
 
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.')
 
679
 
 
680
    @property
 
681
    def image(self):
 
682
        if self._image and not self._verified:
 
683
            self.verify('fix')
 
684
        if self._image is None or self._modified:
 
685
            self._image = self._format_image()
 
686
        return self._image
 
687
 
 
688
    @property
 
689
    @deprecated('3.1', alternative='the `.image` attribute')
267
690
    def cardimage(self):
268
 
        if not hasattr(self, '_cardimage'):
269
 
            self._ascardimage()
270
 
        return self._cardimage
271
 
 
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.
275
 
        """
276
 
 
277
 
        self._cardimage = self._generate_cardimage()
278
 
 
279
 
    def _generate_cardimage(self):
280
 
        """Generate a (new) card image from the attributes: `key`, `value`,
281
 
        and `comment`.  Core code for `ascardimage`.
282
 
        """
283
 
 
284
 
        key_str = self._format_key()
285
 
        val_str = self._format_value()
286
 
        is_commentary = key_str.strip() in Card._commentary_keys
287
 
        if is_commentary:
288
 
            comment_str = ''
289
 
        else:
290
 
            comment_str = self._format_comment()
291
 
 
292
 
        # equal sign string
293
 
        eq_str = '= '
294
 
        if is_commentary:  # not using self.key
295
 
            eq_str = ''
296
 
            if hasattr(self, '_value'):
297
 
                # Value must be a string in commentary cards
298
 
                val_str = str(self.value)
299
 
 
300
 
        # put all parts together
301
 
        output = ''.join([key_str, eq_str, val_str, comment_str])
302
 
 
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
307
 
        else:
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 \
312
 
                   key_str[-1] == ' ':
313
 
                    output = ''.join([key_str[:-1], eq_str, val_str,
314
 
                                      comment_str])
315
 
                else:
316
 
                    raise ValueError('The keyword %s with its value is too '
317
 
                                     'long.' % self.key)
318
 
 
319
 
        if len(output) <= Card.length:
320
 
            output = '%-80s' % output
321
 
 
322
 
        # longstring case (CONTINUE card)
323
 
        else:
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()
330
 
            else:
331
 
                warnings.warn('Card is too long, comment is truncated.')
332
 
                output = output[:Card.length]
333
 
 
334
 
        return output
335
 
 
336
 
    def _format_key(self):
337
 
        # keyword string (check both _key and _cardimage attributes to avoid an
338
 
        # infinite loop
339
 
        if hasattr(self, '_key') or hasattr(self, '_cardimage'):
340
 
            return '%-8s' % self.key
341
 
        else:
342
 
            return ' ' * 8
343
 
 
344
 
    def _format_value(self):
345
 
        # value string
346
 
        if not (hasattr(self, '_value') or hasattr(self, '_cardimage')):
347
 
            return ''
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
354
 
        else:
355
 
            return _value_to_string(self.value)
356
 
 
357
 
    def _format_comment(self):
358
 
        # comment string
359
 
        if hasattr(self, '_comment') or hasattr(self, '_cardimage'):
360
 
            if not self.comment:
361
 
                return ''
362
 
            else:
363
 
                return ' / ' + self._comment
364
 
        else:
365
 
            return ''
366
 
 
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')
 
691
        return self.image
 
692
 
 
693
    @deprecated('3.1', alternative='the `.image` attribute')
370
694
    def ascardimage(self, option='silentfix'):
371
 
        return self._ascardimage(option)
372
 
 
373
 
    def _ascardimage(self, option='silentfix'):
374
 
        """
375
 
        Generate a (new) card image from the attributes: `key`, `value`,
376
 
        and `comment`, or from raw string.
377
 
 
378
 
        Parameters
379
 
        ----------
380
 
        option : str
381
 
            Output verification option.  Must be one of ``"fix"``,
382
 
            ``"silentfix"``, ``"ignore"``, ``"warn"``, or
383
 
            ``"exception"``.  See :ref:`verify` for more info.
384
 
        """
385
 
 
386
 
        # Only if the card image already exist (to avoid infinite loop),
387
 
        # fix it first.
388
 
        if hasattr(self, '_cardimage'):
389
 
            self._check(option)
390
 
        self._update_cardimage()
391
 
        return self._cardimage
 
695
        if not self._verified:
 
696
            self.verify(option)
 
697
        return self.image
392
698
 
393
699
    @classmethod
394
 
    def fromstring(cls, cardimage):
395
 
        """
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).
400
 
        """
401
 
 
402
 
        cardimage = _pad(cardimage)
403
 
 
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()
409
 
        else:
410
 
            card = cls()
411
 
 
412
 
        card._cardimage = cardimage
 
700
    def fromstring(cls, image):
 
701
        """
 
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``
 
705
        card(s).
 
706
        """
 
707
 
 
708
        card = cls()
 
709
        card._image = _pad(image)
 
710
        card._verified = False
413
711
        return card
414
712
 
415
 
    def _check_text(self, val):
416
 
        """Verify `val` to be printable ASCII text."""
417
 
 
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)
422
 
 
423
 
    def _check_key(self, key):
424
 
        """Verify the keyword `key` to be FITS standard."""
425
 
 
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)
431
 
 
432
 
    def _extract_value(self):
 
713
    @classmethod
 
714
    def normalize_keyword(cls, keyword):
 
715
        """
 
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.
 
719
 
 
720
        Parameters
 
721
        ----------
 
722
        key : or str
 
723
            A keyword value or a ``keyword.field-specifier`` value
 
724
 
 
725
        Returns
 
726
        -------
 
727
            The converted string
 
728
        """
 
729
 
 
730
        match = cls._rvkc_keyword_name_RE.match(keyword)
 
731
 
 
732
        if match:
 
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()
 
740
        else:
 
741
            return keyword.strip().upper()
 
742
 
 
743
    def _check_if_rvkc(self, *args):
 
744
        """
 
745
        Determine whether or not the card is a record-valued keyword card.
 
746
 
 
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:
 
752
        value' string.
 
753
 
 
754
        If the check passes the ._keyword, ._value, and .field_specifier
 
755
        keywords are set.
 
756
 
 
757
        Examples
 
758
        --------
 
759
 
 
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')
 
763
        """
 
764
 
 
765
        if not pyfits.ENABLE_RECORD_VALUED_KEYWORD_CARDS:
 
766
            return False
 
767
 
 
768
        if len(args) == 1:
 
769
            image = args[0]
 
770
            eq_idx = image.find('=')
 
771
            if eq_idx < 0 or eq_idx > 9:
 
772
                return False
 
773
            keyword, rest = image.split('=', 1)
 
774
            match = self._rvkc_keyword_val_comm_RE.match(rest)
 
775
            if match:
 
776
                field_specifier = match.group('keyword')
 
777
                self._keyword = '.'.join((keyword.strip().upper(),
 
778
                                          field_specifier))
 
779
                self._field_specifier = field_specifier
 
780
                self._value = _int_or_float(match.group('val'))
 
781
                return True
 
782
        elif len(args) == 2:
 
783
            keyword, value = args
 
784
            if not isinstance(keyword, basestring):
 
785
                return False
 
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(),
 
790
                                          field_specifier))
 
791
                self._field_specifier = field_specifier
 
792
                self._value = value
 
793
                return True
 
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(),
 
799
                                              field_specifier))
 
800
                    self._field_specifier = field_specifier
 
801
                    self._value = _int_or_float(match.group('val'))
 
802
                    return True
 
803
 
 
804
    def _parse_keyword(self):
 
805
        if self._check_if_rvkc(self._image):
 
806
            return self._keyword
 
807
 
 
808
        keyword = self._image[:8].strip()
 
809
        keyword_upper = keyword.upper()
 
810
        value_indicator = self._image.find('= ')
 
811
 
 
812
        special = self._commentary_keywords + ['CONTINUE']
 
813
 
 
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]
 
820
 
 
821
            if keyword_upper != keyword:
 
822
                self._modified = True
 
823
            return keyword_upper
 
824
        elif (keyword_upper == 'HIERARCH' and self._image[8] == ' ' and
 
825
              '=' in self._image):
 
826
            # This is valid HIERARCH card as described by the HIERARCH keyword
 
827
            # convention:
 
828
            # http://fits.gsfc.nasa.gov/registry/hierarch_keyword.html
 
829
            self._hierarch = True
 
830
            return self._image.split('=', 1)[0][9:].rstrip()
 
831
        else:
 
832
            warnings.warn('The following header keyword is invalid or follows '
 
833
                          'an unrecognized non-standard convention:\n%s' %
 
834
                          self._image)
 
835
            self._invalid = True
 
836
            return keyword
 
837
 
 
838
    def _parse_value(self):
433
839
        """Extract the keyword value from the card image."""
434
840
 
435
841
        # for commentary cards, no need to parse further
436
 
        if self.key in Card._commentary_keys:
437
 
            return self.cardimage[8:].rstrip()
438
 
 
439
 
        valu = self._check(option='parse')
440
 
 
441
 
        if valu is None:
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()
 
845
 
 
846
        if self._check_if_rvkc(self._image):
 
847
            return self._value
 
848
 
 
849
        if len(self._image) > self.length:
 
850
            values = []
 
851
            for card in self._itersubcards():
 
852
                value = card.value.rstrip().replace("''", "'")
 
853
                if value and value[-1] == '&':
 
854
                    value = value[:-1]
 
855
                values.append(value)
 
856
 
 
857
            value = ''.join(values)
 
858
 
 
859
            self._valuestring = value
 
860
            return value
 
861
 
 
862
        m = self._value_NFSC_RE.match(self._split()[1])
 
863
 
 
864
        if m is None:
 
865
            raise VerifyError("Unparsable card (%s), fix it first with "
 
866
                              ".verify('fix')." % self.key)
 
867
 
 
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:
453
 
                _sign = ''
 
877
                sign = ''
454
878
            else:
455
 
                _sign = numr.group('sign')
456
 
            _val = _str_to_num(_sign + _digt)
 
879
                sign = numr.group('sign')
 
880
            value = _str_to_num(sign + digt)
457
881
 
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:
463
 
                _rsign = ''
 
887
                rsign = ''
464
888
            else:
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:
470
 
                _isign = ''
 
894
                isign = ''
471
895
            else:
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
474
898
        else:
475
 
            _val = UNDEFINED
476
 
 
477
 
        if not hasattr(self, '_valuestring'):
478
 
            self._valuestring = valu.group('valu')
479
 
        if not hasattr(self, '_value_modified'):
480
 
            self._value_modified = False
481
 
        return _val
482
 
 
483
 
    def _extract_comment(self):
 
899
            value = UNDEFINED
 
900
 
 
901
        if not self._valuestring:
 
902
            self._valuestring = m.group('valu')
 
903
        return value
 
904
 
 
905
    def _parse_comment(self):
484
906
        """Extract the keyword value from the card image."""
485
907
 
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:
488
911
            return ''
489
912
 
490
 
        valu = self._check(option='parse')
491
 
        if valu is not None:
492
 
            comm = valu.group('comm')
493
 
            if isinstance(comm, str):
494
 
                return  comm.rstrip()
 
913
        if len(self._image) > self.length:
 
914
            comments = []
 
915
            for card in self._itersubcards():
 
916
                if card.comment:
 
917
                    comments.append(card.comment)
 
918
            comment = '/ ' + ' '.join(comments).rstrip()
 
919
            m = self._value_NFSC_RE.match(comment)
 
920
        else:
 
921
            m = self._value_NFSC_RE.match(self._split()[1])
 
922
 
 
923
        if m is not None:
 
924
            comment = m.group('comm')
 
925
            if comment:
 
926
                return comment.rstrip()
495
927
        return ''
496
928
 
497
 
    def _fix_value(self, input):
 
929
    def _split(self):
 
930
        """
 
931
        Split the card image between the keyword and the rest of the card.
 
932
        """
 
933
 
 
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
 
937
            image = self._image
 
938
        else:
 
939
            image = self.image
 
940
 
 
941
        if self.keyword in self._commentary_keywords + ['CONTINUE']:
 
942
            keyword, valuecomment = image.split(' ', 1)
 
943
        else:
 
944
            try:
 
945
                delim_index = image.index('= ')
 
946
            except ValueError:
 
947
                delim_index = None
 
948
 
 
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:
 
952
                keyword = image[:8]
 
953
                valuecomment = image[8:]
 
954
            elif delim_index > 10 and image[:9] != 'HIERARCH ':
 
955
                keyword = image[:8]
 
956
                valuecomment = image[8:]
 
957
            else:
 
958
                keyword, valuecomment = image.split('= ', 1)
 
959
        return keyword.strip(), valuecomment.strip()
 
960
 
 
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])
 
965
        else:
 
966
            self._keyword = self._keyword.upper()
 
967
        self._modified = True
 
968
 
 
969
    def _fix_value(self):
498
970
        """Fix the card image for fixable non-standard compliance."""
499
971
 
500
 
        _val_str = None
 
972
        value = None
 
973
        keyword, valuecomment = self._split()
 
974
        m = self._value_NFSC_RE.match(valuecomment)
501
975
 
502
976
        # for the unparsable case
503
 
        if input is None:
504
 
            _tmp = self._get_value_comment_string()
 
977
        if m is None:
505
978
            try:
506
 
                slash_loc = _tmp.index("/")
507
 
                self._value = _tmp[:slash_loc].strip()
508
 
                self._comment = _tmp[slash_loc + 1:].strip()
509
 
            except:
510
 
                self._value = _tmp.strip()
511
 
 
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
 
985
            return
 
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
517
991
 
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
523
997
 
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)
529
 
 
530
 
        self._valuestring = _val_str
531
 
        self._update_cardimage()
532
 
 
533
 
    def _locate_eq(self):
534
 
        """
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.
538
 
        """
539
 
 
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:
543
 
            eq_loc = None
544
 
        else:
545
 
            if _key == 'HIERARCH':
546
 
                _limit = Card.length
547
 
            else:
548
 
                _limit = 10
549
 
            try:
550
 
                eq_loc = self.cardimage[:_limit].index("=")
551
 
            except:
552
 
                eq_loc = None
553
 
        return eq_loc
554
 
 
555
 
    def _get_key_string(self):
556
 
        """
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.
560
 
        """
561
 
 
562
 
        eq_loc = self._locate_eq()
563
 
        if eq_loc is None:
564
 
            eq_loc = 8
565
 
        start = 0
566
 
        if self.cardimage[:8].upper() == 'HIERARCH':
567
 
            start = 8
568
 
            self.__class__ = _HierarchCard
569
 
        return self.cardimage[start:eq_loc]
570
 
 
571
 
    def _get_value_comment_string(self):
572
 
        """
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.
576
 
        """
577
 
 
578
 
        eq_loc = self._locate_eq()
579
 
        if eq_loc is None:
580
 
            eq_loc = 7
581
 
        return self.cardimage[eq_loc+1:]
582
 
 
583
 
    def _check(self, option='ignore'):
584
 
        """Verify the card image with the specified option."""
585
 
 
586
 
        self._err_text = ''
587
 
        self._fix_text = ''
588
 
        self._fixable = True
589
 
 
590
 
        if option == 'ignore':
591
 
            return
592
 
        elif option == 'parse':
593
 
 
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())
596
 
 
597
 
            # if not parsable (i.e. everything else) result = None
598
 
            return result
599
 
        else:
600
 
 
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']:
605
 
                    self._err_text = \
606
 
                        'Card image is not FITS standard (equal sign not at ' \
607
 
                        'column 8).'
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)
612
 
                    if option == 'fix':
613
 
                        self._fix_text = \
614
 
                            'Fixed card to be FITS standard.: %s' % self.key
615
 
 
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)
620
 
 
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:
625
 
                return result
626
 
            else:
627
 
                if option in ['fix', 'silentfix']:
628
 
                    result = self._check('parse')
629
 
                    self._fix_value(result)
630
 
                    if option == 'fix':
631
 
                        self._fix_text = \
632
 
                            'Fixed card to be FITS standard.: %s' % self.key
633
 
                else:
634
 
                    self._err_text = \
635
 
                        'Card image is not FITS standard (unparsable value ' \
636
 
                        'string: %s).' % value
637
 
                    raise ValueError(self._err_text + '\n%s' % self.cardimage)
638
 
 
639
 
            # verify the comment (string), it is never fixable
640
 
            if result is not None:
641
 
                comm = result.group('comm')
642
 
                if comm is not None:
643
 
                    self._check_text(comm)
644
 
 
645
 
    def _ncards(self):
646
 
        return len(self.cardimage) // Card.length
647
 
 
648
 
    def _verify(self, option='warn'):
649
 
        """Card class verification method."""
650
 
 
651
 
        err = _ErrList([])
652
 
        try:
653
 
            self._check(option)
654
 
        except ValueError:
655
 
            # Trapping the ValueError raised by _check method.  Want execution to continue while printing
656
 
            # exception message.
657
 
            pass
658
 
        err.append(self.run_option(option, err_text=self._err_text,
659
 
                   fix_text=self._fix_text, fixable=self._fixable))
660
 
 
661
 
        return err
662
 
 
663
 
 
664
 
class RecordValuedKeywordCard(Card):
665
 
    """
666
 
    Class to manage record-valued keyword cards as described in the
667
 
    FITS WCS Paper IV proposal for representing a more general
668
 
    distortion model.
669
 
 
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
673
 
    following syntax::
674
 
 
675
 
        keyword = 'field-specifier: float'
676
 
 
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::
681
 
 
682
 
        field-specifier:
683
 
            field
684
 
            field-specifier.field
685
 
 
686
 
        field:
687
 
            identifier
688
 
            identifier.index
689
 
 
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
695
 
    for that purpose.
696
 
 
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.
700
 
 
701
 
    Some examples follow::
702
 
 
703
 
        DP1     = 'NAXIS: 2'
704
 
        DP1     = 'AXIS.1: 1'
705
 
        DP1     = 'AXIS.2: 2'
706
 
        DP1     = 'NAUX: 2'
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'
711
 
    """
712
 
 
713
 
    #
714
 
    # A group of class level regular expression definitions that allow the
715
 
    # extraction of the key, field-specifier, value, and comment from a
716
 
    # card string.
717
 
    #
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
729
 
    #
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
732
 
    # Compliant
733
 
    #
734
 
    field_specifier_NFSC_image_RE = re.compile(field_specifier_NFSC_val)
735
 
    #
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')
739
 
    #
740
 
    field_specifier_NFSC_val_RE = re.compile(field_specifier_NFSC_val + r'$')
741
 
    #
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')
745
 
    #
746
 
    keyword_name_RE = re.compile(r'(?P<key>%s)\.(?P<field_spec>%s)$'
747
 
                                 % (identifier, field_specifier_s))
748
 
    #
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")
752
 
    #
753
 
    keyword_val_comm_RE = re.compile(keyword_val_comm)
754
 
    #
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")
758
 
    #
759
 
    keyword_NFSC_val_comm_RE = re.compile(keyword_NFSC_val_comm)
760
 
 
761
 
    def __init__(self, key='', value='', comment=''):
762
 
        """
763
 
        Parameters
764
 
        ----------
765
 
        key : str, optional
766
 
            The key, either the simple key or one that contains
767
 
            a field-specifier
768
 
 
769
 
        value : str, optional
770
 
            The value, either a simple value or one that contains a
771
 
            field-specifier
772
 
 
773
 
        comment : str, optional
774
 
            The comment
775
 
        """
776
 
 
777
 
        mo = self.keyword_name_RE.match(key)
778
 
 
779
 
        if mo:
780
 
            self._field_specifier = mo.group('field_spec')
781
 
            key = mo.group('key')
782
 
        else:
783
 
            if isinstance(value, str):
784
 
                if value:
785
 
                    mo = self.field_specifier_NFSC_val_RE.match(value)
786
 
 
787
 
                    if mo:
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
792
 
                        # error
793
 
                        try:
794
 
                            value = int(value)
795
 
                        except ValueError:
796
 
                            try:
797
 
                                value = float(value)
798
 
                            except ValueError:
799
 
                                raise ValueError(
800
 
                                    "Record-valued keyword card value must be "
801
 
                                    "a floating point or integer value.")
802
 
                    else:
803
 
                        raise ValueError(
804
 
                            "Value %s must be in the form "
805
 
                            "field_specifier: value (ex. 'NAXIS: 2')" % value)
806
 
            else:
807
 
                raise ValueError('value %s is not a string' % value)
808
 
 
809
 
        super(RecordValuedKeywordCard, self).__init__(key, value, comment)
810
 
 
811
 
    @property
812
 
    def field_specifier(self):
813
 
       self._extract_value()
814
 
       return self._field_specifier
815
 
 
816
 
    @property
817
 
    def raw(self):
818
 
        """
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.
822
 
        """
823
 
 
824
 
        key = super(RecordValuedKeywordCard, self)._getkey()
825
 
        return Card(key, self.strvalue(), self.comment)
826
 
 
827
 
    def _getkey(self):
828
 
        key = super(RecordValuedKeywordCard, self)._getkey()
829
 
        if not hasattr(self, '_field_specifier'):
830
 
            return key
831
 
        return '%s.%s' % (key, self._field_specifier)
832
 
 
833
 
    key = property(_getkey, Card.key.fset, doc=Card.key.__doc__)
834
 
 
835
 
    def _getvalue(self):
836
 
        """The RVKC value should always be returned as a float."""
837
 
 
838
 
        return float(super(RecordValuedKeywordCard, self)._getvalue())
839
 
 
840
 
    def _setvalue(self, val):
841
 
        if not isinstance(val, float):
842
 
            try:
843
 
                val = int(val)
844
 
            except ValueError:
845
 
                try:
846
 
                    val = float(val)
847
 
                except:
848
 
                    raise ValueError('value %s is not a float' % val)
849
 
        super(RecordValuedKeywordCard, self)._setvalue(val)
850
 
 
851
 
    value = property(_getvalue, _setvalue, doc=Card.value.__doc__)
852
 
 
853
 
    #
854
 
    # class method definitins
855
 
    #
856
 
 
857
 
    @classmethod
858
 
    def coerce(cls, card):
859
 
        """
860
 
        Coerces an input `Card` object to a `RecordValuedKeywordCard`
861
 
        object if the value of the card meets the requirements of this
862
 
        type of card.
863
 
 
864
 
        Parameters
865
 
        ----------
866
 
        card : `Card` object
867
 
            A `Card` object to coerce
868
 
 
869
 
        Returns
870
 
        -------
871
 
        card
872
 
            - If the input card is coercible:
873
 
 
874
 
                a new `RecordValuedKeywordCard` constructed from the
875
 
                `key`, `value`, and `comment` of the input card.
876
 
 
877
 
            - If the input card is not coercible:
878
 
 
879
 
                the input card
880
 
        """
881
 
 
882
 
        mo = cls.field_specifier_NFSC_val_RE.match(card.value)
883
 
        if mo:
884
 
            return cls(card.key, card.value, card.comment)
885
 
        else:
886
 
            return card
887
 
 
888
 
    @classmethod
889
 
    def upper_key(cls, key):
890
 
        """
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
894
 
        case.
895
 
 
896
 
        Parameters
897
 
        ----------
898
 
        key : int or str
899
 
            A keyword value that could be an integer, a key, or a
900
 
            `key.field-specifier` value
901
 
 
902
 
        Returns
903
 
        -------
904
 
        Integer input
905
 
            the original integer key
906
 
 
907
 
        String input
908
 
            the converted string
909
 
        """
910
 
 
911
 
        if _is_int(key):
912
 
            return key
913
 
 
914
 
        mo = cls.keyword_name_RE.match(key)
915
 
 
916
 
        if mo:
917
 
            return mo.group('key').strip().upper() + '.' + \
918
 
                   mo.group('field_spec')
919
 
        else:
920
 
            return key.strip().upper()
921
 
    # For API backwards-compatibility
922
 
    upperKey = \
923
 
        deprecated(name='upperKey', alternative='upper_key()')(upper_key)
924
 
 
925
 
    @classmethod
926
 
    def valid_key_value(cls, key, value=0):
927
 
        """
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
934
 
        the field specifier.
935
 
 
936
 
        Parameters
937
 
        ----------
938
 
        key : str
939
 
            The key to parse
940
 
 
941
 
        value : str or float-like, optional
942
 
            The value to parse
943
 
 
944
 
        Returns
945
 
        -------
946
 
        valid input : A list containing the key, field-specifier, value
947
 
 
948
 
        invalid input : An empty list
949
 
 
950
 
        Examples
951
 
        --------
952
 
 
953
 
        >>> valid_key_value('DP1','AXIS.1: 2')
954
 
        >>> valid_key_value('DP1.AXIS.1', 2)
955
 
        >>> valid_key_value('DP1.AXIS.1')
956
 
        """
957
 
 
958
 
        rtnKey = rtnFieldSpec = rtnValue = ''
959
 
        myKey = cls.upper_key(key)
960
 
 
961
 
        if isinstance(myKey, basestring):
962
 
            validKey = cls.keyword_name_RE.match(myKey)
963
 
 
964
 
            if validKey:
965
 
               try:
966
 
                   rtnValue = float(value)
967
 
               except ValueError:
968
 
                   pass
969
 
               else:
970
 
                   rtnKey = validKey.group('key')
971
 
                   rtnFieldSpec = validKey.group('field_spec')
972
 
            else:
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)
976
 
                    if validValue:
977
 
                        rtnFieldSpec = validValue.group('keyword')
978
 
                        rtnValue = validValue.group('val')
979
 
                        rtnKey = myKey
980
 
 
981
 
        if rtnFieldSpec:
982
 
            return [rtnKey, rtnFieldSpec, rtnValue]
983
 
        else:
984
 
            return []
985
 
    # For API backwards-compatibility
986
 
    validKeyValue = \
987
 
        deprecated(name='validKeyValue',
988
 
                   alternative='valid_key_value()')(valid_key_value)
989
 
 
990
 
    @classmethod
991
 
    def create(cls, key='', value='', comment=''):
992
 
        """
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.
997
 
 
998
 
        Parameters
999
 
        ----------
1000
 
        key : str, optional
1001
 
            The key
1002
 
 
1003
 
        value : str, optional
1004
 
            The value
1005
 
 
1006
 
        comment : str, optional
1007
 
            The comment
1008
 
 
1009
 
        Returns
1010
 
        -------
1011
 
        card
1012
 
            Either a `RecordValuedKeywordCard` or a `Card` object.
1013
 
        """
1014
 
 
1015
 
        if not cls.valid_key_value(key, value):
1016
 
            # This should be just a normal card
1017
 
            cls = Card
1018
 
 
1019
 
        return cls(key, value, comment)
1020
 
    # For API backwards-compatibility
1021
 
    createCard = deprecated(name='createCard', alternative='create()')(create)
1022
 
 
1023
 
    @classmethod
1024
 
    def fromstring(cls, input):
1025
 
        """
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.
1030
 
 
1031
 
        Parameters
1032
 
        ----------
1033
 
        input : str
1034
 
            The string representing the card
1035
 
 
1036
 
        Returns
1037
 
        -------
1038
 
        card
1039
 
            either a `RecordValuedKeywordCard` or a `Card` object
1040
 
        """
1041
 
 
1042
 
        idx1 = input.find("'") + 1
1043
 
        idx2 = input.rfind("'")
1044
 
 
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)
1050
 
        else:
1051
 
            # This calls Card.fromstring directly, creating a plain Card
1052
 
            # object.
1053
 
            return Card.fromstring(input)
1054
 
 
1055
 
    # For API backwards-compatibility
1056
 
    createCardFromString = deprecated(name='createCardFromString',
1057
 
                                      alternative='fromstring()')(fromstring)
1058
 
 
1059
 
    def _update_cardimage(self):
1060
 
        """
1061
 
        Generate a (new) card image from the attributes: `key`, `value`,
1062
 
        `field_specifier`, and `comment`.  Core code for `ascardimage`.
1063
 
        """
1064
 
 
1065
 
        super(RecordValuedKeywordCard, self)._update_cardimage()
1066
 
        eqloc = self._cardimage.index('=')
1067
 
        slashloc = self._cardimage.find('/')
1068
 
 
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()
1074
 
        else:
1075
 
            val_str = self._valuestring
1076
 
 
1077
 
        val_str = "'%s: %s'" % (self._field_specifier, val_str)
1078
 
        val_str = '%-20s' % val_str
1079
 
 
1080
 
        output = self._cardimage[:eqloc+2] + val_str
1081
 
 
1082
 
        if slashloc > 0:
1083
 
            output = output + self._cardimage[slashloc-1:]
1084
 
 
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
 
1008
 
 
1009
    def _format_keyword(self):
 
1010
        if self.keyword:
 
1011
            if self.field_specifier:
 
1012
                return '%-8s' % self.keyword.split('.', 1)[0]
 
1013
            elif self._hierarch:
 
1014
                return 'HIERARCH %s ' % self.keyword
 
1015
            else:
 
1016
                return '%-8s' % self.keyword
 
1017
        else:
 
1018
            return ' ' * 8
 
1019
 
 
1020
    def _format_value(self):
 
1021
        # value string
 
1022
        float_types = (float, np.floating, complex, np.complexfloating)
 
1023
 
 
1024
        # Force the value to be parsed out first
 
1025
        value = self.value
 
1026
        # But work with the underlying raw value instead (to preserve
 
1027
        # whitespace, for now...)
 
1028
        value = self._value
 
1029
 
 
1030
        if self.keyword in self._commentary_keywords:
 
1031
            # The value of a commentary card must be just a raw unprocessed
 
1032
            # string
 
1033
            value = str(value)
 
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)
 
1041
        else:
 
1042
            value = _format_value(value)
 
1043
 
 
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()
 
1047
 
 
1048
        return value
 
1049
 
 
1050
    def _format_comment(self):
 
1051
        if not self.comment:
 
1052
            return ''
 
1053
        else:
 
1054
            return ' / %s' % self._comment
 
1055
 
 
1056
    def _format_image(self):
 
1057
        keyword = self._format_keyword()
 
1058
 
 
1059
        value = self._format_value()
 
1060
        is_commentary = keyword.strip() in self._commentary_keywords
 
1061
        if is_commentary:
 
1062
            comment = ''
 
1063
        else:
 
1064
            comment = self._format_comment()
 
1065
 
 
1066
        # equal sign string
 
1067
        delimiter = '= '
 
1068
        if is_commentary:
 
1069
            delimiter = ''
 
1070
 
 
1071
        # put all parts together
 
1072
        output = ''.join([keyword, delimiter, value, comment])
 
1073
 
 
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])
 
1082
            else:
 
1083
                # I guess the HIERARCH card spec is incompatible with CONTINUE
 
1084
                # cards
 
1085
                raise ValueError('The keyword %s with its value is too long' %
 
1086
                                 self.keyword)
 
1087
 
 
1088
        if len(output) <= self.length:
1086
1089
            output = '%-80s' % output
1087
 
 
1088
 
        self._cardimage = output
1089
 
 
1090
 
 
1091
 
    def _extract_value(self):
1092
 
        """Extract the keyword value from the card image."""
1093
 
 
1094
 
        valu = self._check(option='parse')
1095
 
 
1096
 
        if valu is None:
1097
 
            raise ValueError(
1098
 
                "Unparsable card, fix it first with .verify('fix').")
1099
 
 
1100
 
        self._field_specifier = valu.group('keyword')
1101
 
 
1102
 
        if not hasattr(self, '_valuestring'):
1103
 
            self._valuestring = valu.group('val')
1104
 
        if not hasattr(self, '_value_modified'):
1105
 
            self._value_modified = False
1106
 
 
1107
 
        return _str_to_num(translate(valu.group('val'), FIX_FP_TABLE2, ' '))
1108
 
 
1109
 
    def strvalue(self):
1110
 
        """
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.
1115
 
        """
1116
 
 
1117
 
        mo = self.field_specifier_NFSC_image_RE.search(self.cardimage)
1118
 
        return self.cardimage[mo.start():mo.end()]
1119
 
 
1120
 
    def _fix_value(self, input):
1121
 
        """Fix the card image for fixable non-standard compliance."""
1122
 
 
1123
 
        _val_str = None
1124
 
 
1125
 
        if input is None:
1126
 
            tmp = self._get_value_comment_string()
1127
 
 
1128
 
            try:
1129
 
                slash_loc = tmp.index("/")
1130
 
            except:
1131
 
                slash_loc = len(tmp)
1132
 
 
1133
 
            self._err_text = 'Illegal value %s' % tmp[:slash_loc]
1134
 
            self._fixable = False
1135
 
            raise ValueError(self._err_text)
1136
 
        else:
1137
 
            self._valuestring = translate(input.group('val'), FIX_FP_TABLE,
1138
 
                                          ' ')
1139
 
            self._update_cardimage()
1140
 
 
1141
 
    def _format_key(self):
1142
 
        if hasattr(self, '_key') or hasattr(self, '_cardimage'):
1143
 
            return '%-8s' % super(RecordValuedKeywordCard, self).key
1144
 
        else:
1145
 
            return ' ' * 8
1146
 
 
1147
 
    def _check_key(self, key):
1148
 
        """
1149
 
        Verify the keyword to be FITS standard and that it matches the
1150
 
        standard for record-valued keyword cards.
1151
 
        """
1152
 
 
1153
 
        if '.' in key:
1154
 
            keyword, field_specifier = key.split('.', 1)
1155
 
        else:
1156
 
            keyword, field_specifier = key, None
1157
 
 
1158
 
        super(RecordValuedKeywordCard, self)._check_key(keyword)
1159
 
 
1160
 
        if field_specifier:
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)
1166
 
 
1167
 
    def _check(self, option='ignore'):
1168
 
        """Verify the card image with the specified `option`."""
1169
 
 
1170
 
        self._err_text = ''
1171
 
        self._fix_text = ''
1172
 
        self._fixable = True
1173
 
 
1174
 
        if option == 'ignore':
1175
 
            return
1176
 
        elif option == 'parse':
1177
 
            return self.keyword_NFSC_val_comm_RE.match(
1178
 
                    self._get_value_comment_string())
1179
 
        else:
1180
 
            # verify the equal sign position
1181
 
            if self.cardimage.find('=') != 8:
1182
 
                if option in ['exception', 'warn']:
1183
 
                    self._err_text = \
1184
 
                        'Card image is not FITS standard (equal sign not at ' \
1185
 
                        'column 8).'
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)
1190
 
 
1191
 
                    if option == 'fix':
1192
 
                        self._fix_text = \
1193
 
                           'Fixed card to be FITS standard. : %s' % self.key
1194
 
 
1195
 
            # verify the key
1196
 
            self._check_key(self.key)
1197
 
 
1198
 
            # verify the value
1199
 
            result = \
1200
 
              self.keyword_val_comm_RE.match(self._get_value_comment_string())
1201
 
 
1202
 
            if result is not None:
1203
 
                return result
1204
 
            else:
1205
 
                if option in ['fix', 'silentfix']:
1206
 
                    result = self._check('parse')
1207
 
                    self._fix_value(result)
1208
 
 
1209
 
                    if option == 'fix':
1210
 
                        self._fix_text = \
1211
 
                              'Fixed card to be FITS standard.: %s' % self.key
1212
 
                else:
1213
 
                    self._err_text = \
1214
 
                        'Card image is not FITS standard (unparsable value ' \
1215
 
                        'string).'
1216
 
                    raise ValueError(self._err_text + '\n%s' % self.cardimage)
1217
 
 
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)
1223
 
 
1224
 
 
1225
 
class CardList(list):
1226
 
    """FITS header card list class."""
1227
 
 
1228
 
    def __init__(self, cards=[], keylist=None):
1229
 
        """
1230
 
        Construct the `CardList` object from a list of `Card` objects.
1231
 
 
1232
 
        Parameters
1233
 
        ----------
1234
 
        cards
1235
 
            A list of `Card` objects.
1236
 
        """
1237
 
 
1238
 
        super(CardList, self).__init__(cards)
1239
 
 
1240
 
        # if the key list is not supplied (as in reading in the FITS file),
1241
 
        # it will be constructed from the card list.
1242
 
        if keylist is None:
1243
 
            self._keys = []
1244
 
            for c in self:
1245
 
                if isinstance(c, RecordValuedKeywordCard):
1246
 
                    self._keys.append(c.raw.key)
1247
 
                else:
1248
 
                    self._keys.append(c.key)
1249
 
        else:
1250
 
            self._keys = keylist
1251
 
 
1252
 
        # find out how many blank cards are *directly* before the END card
1253
 
        self._blanks = 0
1254
 
        self.count_blanks()
1255
 
        self._mod = False
1256
 
 
1257
 
    def __contains__(self, key):
1258
 
        return upper_key(key) in self._keys
1259
 
 
1260
 
    def __getitem__(self, key):
1261
 
        """Get a `Card` by indexing or by the keyword name."""
1262
 
 
1263
 
        if isinstance(key, slice):
1264
 
            return CardList(super(CardList, self).__getitem__(key),
1265
 
                            self._keys[key])
1266
 
        elif isinstance(key, basestring) and self._has_filter_char(key):
1267
 
            return self.filter_list(key)
1268
 
        else:
1269
 
            idx = self.index_of(key)
1270
 
            return super(CardList, self).__getitem__(idx)
1271
 
 
1272
 
    def __setitem__(self, key, value):
1273
 
        """Set a `Card` by indexing or by the keyword name."""
1274
 
 
1275
 
        if isinstance(value, Card):
1276
 
            idx = self.index_of(key)
1277
 
 
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()
1283
 
                else:
1284
 
                    self._keys[idx] = value.key.upper()
1285
 
                self.count_blanks()
1286
 
                self._mod = True
1287
 
        else:
1288
 
            raise ValueError('%s is not a Card' % str(value))
1289
 
 
1290
 
    def __delitem__(self, key):
1291
 
        """Delete a `Card` from the `CardList`."""
1292
 
 
1293
 
        if self._has_filter_char(key):
1294
 
            cardlist = self.filter_list(key)
1295
 
 
1296
 
            if len(cardlist) == 0:
1297
 
                raise KeyError("Keyword '%s' not found." % key)
1298
 
 
1299
 
            for card in cardlist:
1300
 
                key = card.key
1301
 
                del self[key]
1302
 
        else:
1303
 
            idx = self.index_of(key)
1304
 
            super(CardList, self).__delitem__(idx)
1305
 
            del self._keys[idx]  # update the keylist
1306
 
            self.count_blanks()
1307
 
            self._mod = True
1308
 
 
1309
 
    def __getslice__(self, start, end):
1310
 
        return self[slice(start, end)]
1311
 
 
1312
 
    def __repr__(self):
1313
 
        """Format a list of cards into a string."""
1314
 
 
1315
 
        return ''.join(map(str, self))
1316
 
 
1317
 
    def __str__(self):
1318
 
        """Format a list of cards into a printable string."""
1319
 
        return '\n'.join(map(str, self))
1320
 
 
1321
 
    def __deepcopy__(self, memo):
1322
 
        dup = CardList([copy.deepcopy(c, memo) for c in self])
1323
 
        memo[id(self)] = dup
1324
 
        return dup
1325
 
 
1326
 
    def _get_mod(self):
1327
 
        mod = self.__dict__.get('_mod', False)
1328
 
        if not mod:
1329
 
            # See if any of the cards were directly modified
1330
 
            for card in self:
1331
 
                if card._modified:
1332
 
                    self.__dict__['_mod'] = True
1333
 
                    return True
1334
 
        return mod
1335
 
 
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
1340
 
        if not value:
1341
 
            for card in self:
1342
 
                card._modified = False
1343
 
 
1344
 
    _mod = property(_get_mod, _set_mod,
1345
 
                    doc='has this card list been modified since last write')
1346
 
 
1347
 
    def copy(self):
1348
 
        """Make a (deep)copy of the `CardList`."""
1349
 
 
1350
 
        return CardList([create_card_from_string(str(c)) for c in self])
1351
 
 
1352
 
    def keys(self):
1353
 
        """
1354
 
        Return a list of all keywords from the `CardList`.
1355
 
 
1356
 
        Keywords include ``field_specifier`` for
1357
 
        `RecordValuedKeywordCard` objects.
1358
 
        """
1359
 
 
1360
 
        return [card.key for card in self]
1361
 
 
1362
 
    def values(self):
1363
 
        """
1364
 
        Return a list of the values of all cards in the `CardList`.
1365
 
 
1366
 
        For `RecordValuedKeywordCard` objects, the value returned is
1367
 
        the floating point value, exclusive of the
1368
 
        ``field_specifier``.
1369
 
        """
1370
 
 
1371
 
        return [c.value for c in self]
1372
 
 
1373
 
    def insert(self, pos, card, useblanks=True):
1374
 
        """
1375
 
        Insert a `Card` to the `CardList`.
1376
 
 
1377
 
        Parameters
1378
 
        ----------
1379
 
        pos : int
1380
 
            The position (index, keyword name will not be allowed) to
1381
 
            insert. The new card will be inserted before it.
1382
 
 
1383
 
        card : `Card` object
1384
 
            The card to be inserted.
1385
 
 
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``.
1393
 
        """
1394
 
 
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())
1399
 
            else:
1400
 
                self._keys.insert(pos, card.key.upper())  # update the keylist
1401
 
            self.count_blanks()
1402
 
            if useblanks:
1403
 
                self._use_blanks(card._ncards())
1404
 
 
1405
 
            self.count_blanks()
1406
 
            self._mod = True
1407
 
        else:
1408
 
            raise ValueError('%s is not a Card' % str(card))
1409
 
 
1410
 
    def append(self, card, useblanks=True, bottom=False):
1411
 
        """
1412
 
        Append a `Card` to the `CardList`.
1413
 
 
1414
 
        Parameters
1415
 
        ----------
1416
 
        card : `Card` object
1417
 
            The `Card` to be appended.
1418
 
 
1419
 
        useblanks : bool, optional
1420
 
            Use any *extra* blank cards?
1421
 
 
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``.
1428
 
 
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.
1433
 
        """
1434
 
 
1435
 
        if isinstance(card, Card):
1436
 
            nc = len(self) - self._blanks
1437
 
            idx = nc - 1
1438
 
            if not bottom:
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:
1442
 
                        break
1443
 
 
1444
 
            super(CardList, self).insert(idx + 1, card)
1445
 
            if isinstance(card, RecordValuedKeywordCard):
1446
 
                self._keys.insert(idx + 1, card.raw.key.upper())
1447
 
            else:
1448
 
                self._keys.insert(idx + 1, card.key.upper())
1449
 
            if useblanks:
1450
 
                self._use_blanks(card._ncards())
1451
 
            self.count_blanks()
1452
 
            self._mod = True
1453
 
        else:
1454
 
            raise ValueError("%s is not a Card" % str(card))
1455
 
 
1456
 
    def index_of(self, key, backward=False):
1457
 
        """
1458
 
        Get the index of a keyword in the `CardList`.
1459
 
 
1460
 
        Parameters
1461
 
        ----------
1462
 
        key : str or int
1463
 
            The keyword name (a string) or the index (an integer).
1464
 
 
1465
 
        backward : bool, optional
1466
 
            When `True`, search the index from the ``END``, i.e.,
1467
 
            backward.
1468
 
 
1469
 
        Returns
1470
 
        -------
1471
 
        index : int
1472
 
            The index of the `Card` with the given keyword.
1473
 
        """
1474
 
 
1475
 
        if _is_int(key):
1476
 
            return key
1477
 
        elif isinstance(key, basestring):
1478
 
            _key = key.strip().upper()
1479
 
            if _key[:8] == 'HIERARCH':
1480
 
                _key = _key[8:].strip()
1481
 
            keys = self._keys
1482
 
            if backward:
1483
 
                # We have to search backwards through they key list
1484
 
                keys = reversed(keys)
1485
 
            try:
1486
 
                idx = keys.index(_key)
1487
 
            except ValueError:
1488
 
                reqkey = RecordValuedKeywordCard.valid_key_value(key)
1489
 
                idx = 0
1490
 
 
1491
 
                while reqkey:
1492
 
                    try:
1493
 
                        jdx = keys[idx:].index(reqkey[0].upper())
1494
 
                        idx += jdx
1495
 
                        card = self[idx]
1496
 
                        if isinstance(card, RecordValuedKeywordCard) and \
1497
 
                           reqkey[1] == card.field_specifier:
1498
 
                            break
1499
 
                    except ValueError:
1500
 
                        raise KeyError('Keyword %s not found.' % repr(key))
1501
 
 
1502
 
                    idx += 1
1503
 
                else:
1504
 
                    raise KeyError('Keyword %s not found.' % repr(key))
1505
 
 
1506
 
            if backward:
1507
 
                idx = len(keys) - idx - 1
1508
 
            return idx
1509
 
        else:
1510
 
            raise KeyError('Illegal key data type %s' % type(key))
1511
 
 
1512
 
    def filter_list(self, key):
1513
 
        """
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 ``...``).
1517
 
 
1518
 
        Parameters
1519
 
        ----------
1520
 
        key : str
1521
 
            key value to filter the list with
1522
 
 
1523
 
        Returns
1524
 
        -------
1525
 
        cardlist :
1526
 
            A `CardList` object containing references to all the
1527
 
            requested cards.
1528
 
        """
1529
 
 
1530
 
        out_cl = CardList()
1531
 
 
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)
1537
 
 
1538
 
        for card in self:
1539
 
            match_str = card.key
1540
 
 
1541
 
            if match_RE.match(match_str):
1542
 
                out_cl.append(card)
1543
 
 
1544
 
        return out_cl
1545
 
    filterList = filter_list # For API backwards-compatibility
1546
 
 
1547
 
    def count_blanks(self):
1548
 
        """
1549
 
        Returns how many blank cards are *directly* before the ``END``
1550
 
        card.
1551
 
        """
1552
 
 
1553
 
        blank = ' ' * Card.length
1554
 
        for idx in range(1, len(self)):
1555
 
            if str(self[-idx]) != blank:
1556
 
                self._blanks = idx - 1
1557
 
                break
1558
 
 
1559
 
    def _has_filter_char(self, key):
1560
 
        """
1561
 
        Return `True` if the input key contains one of the special filtering
1562
 
        characters (``*``, ``?``, or ...).
1563
 
        """
1564
 
 
1565
 
        if isinstance(key, basestring) and \
1566
 
           (key.endswith('...') or key.find('*') > 0 or key.find('?') > 0):
1567
 
            return True
1568
 
        else:
1569
 
            return False
1570
 
 
1571
 
    def _pos_insert(self, card, before, after, useblanks=True, replace=False):
1572
 
        """
1573
 
        Insert a `Card` to the location specified by before or after.
1574
 
 
1575
 
        The argument `before` takes precedence over `after` if both
1576
 
        specified.  They can be either a keyword name or index.
1577
 
        """
1578
 
 
1579
 
        if before is None:
1580
 
            insertionkey = after
1581
 
        else:
1582
 
            insertionkey = before
1583
 
 
1584
 
        def get_insertion_idx():
1585
 
            if not (isinstance(insertionkey, int) and
1586
 
                    insertionkey >= len(self)):
1587
 
                idx = self.index_of(insertionkey)
1588
 
            else:
1589
 
                idx = insertionkey
1590
 
 
1591
 
            if before is None:
1592
 
                idx += 1
1593
 
 
1594
 
            return idx
1595
 
 
1596
 
        if replace:
1597
 
            old_idx = self.index_of(card.key)
1598
 
            insertion_idx = get_insertion_idx()
1599
 
 
1600
 
            if insertion_idx >= len(self) and old_idx == len(self) - 1:
1601
 
                return
1602
 
 
1603
 
            if before is not None:
1604
 
                if old_idx == insertion_idx - 1:
1605
 
                    return
1606
 
            elif after is not None and old_idx == insertion_idx:
1607
 
                return
1608
 
 
1609
 
            del self[old_idx]
1610
 
 
1611
 
        idx = get_insertion_idx()
1612
 
        self.insert(idx, card, useblanks=useblanks)
1613
 
 
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
1618
 
 
1619
 
 
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)
1626
 
 
1627
 
 
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)
1635
 
 
1636
 
def upper_key(key):
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)
1641
 
 
1642
 
 
1643
 
class _HierarchCard(Card):
1644
 
    """
1645
 
    Cards begins with ``HIERARCH`` which allows keyword name longer than 8
1646
 
    characters.
1647
 
    """
1648
 
 
1649
 
    def _getkey(self):
1650
 
        """Returns the keyword name parsed from the card image."""
1651
 
 
1652
 
        if not hasattr(self, '_key'):
1653
 
            head = self._get_key_string()
1654
 
            self._key = head.strip()
1655
 
        return self._key
1656
 
 
1657
 
    def _format_key(self):
1658
 
        if hasattr(self, '_key') or hasattr(self, '_cardimage'):
1659
 
            return 'HIERARCH %s ' % self.key
1660
 
        else:
1661
 
            return ' ' * 8
1662
 
 
1663
 
    def _format_value(self):
1664
 
        val_str = super(_HierarchCard, self)._format_value()
1665
 
        # conserve space for HIERARCH cards
1666
 
        return val_str.strip()
1667
 
 
1668
 
 
1669
 
    def _verify(self, option='warn'):
1670
 
        """No verification (for now)."""
1671
 
 
1672
 
        return _ErrList([])
1673
 
 
1674
 
 
1675
 
class _ContinueCard(Card):
1676
 
    """
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
1679
 
    string value.
1680
 
    """
1681
 
 
1682
 
    def __repr__(self):
1683
 
        """Format a list of cards into a printable string."""
1684
 
 
1685
 
        output = []
1686
 
        for idx in range(len(self.cardimage) // 80):
1687
 
            output.append(self.cardimage[idx*80:(idx+1)*80])
1688
 
        return '\n'.join(output)
1689
 
 
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):
1699
 
                raise ValueError(
1700
 
                    'Cards with CONTINUE must have string value.')
1701
 
            yield card
1702
 
 
1703
 
    def _extract_value(self):
1704
 
        """Extract the keyword value from the card image."""
1705
 
 
1706
 
        output = []
1707
 
        for card in self._iter_cards():
1708
 
            val = card.value.rstrip().replace("''", "'")
1709
 
            # drop the ending "&"
1710
 
            if val and val[-1] == '&':
1711
 
                val = val[:-1]
1712
 
            output.append(val)
1713
 
        return ''.join(output).rstrip()
1714
 
 
1715
 
    def _extract_comment(self):
1716
 
        """Extract the comment from the card image."""
1717
 
 
1718
 
        output = []
1719
 
        for card in self._iter_cards():
1720
 
            comm = card.comment
1721
 
            if isinstance(comm, str) and comm != '':
1722
 
                output.append(comm.rstrip() + ' ')
1723
 
        return ''.join(output).rstrip()
1724
 
 
1725
 
    def _breakup_strings(self):
 
1090
        else:
 
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()
 
1097
            else:
 
1098
                warnings.warn('Card is too long, comment will be truncated.',
 
1099
                              VerifyWarning)
 
1100
                output = output[:Card.length]
 
1101
        return output
 
1102
 
 
1103
    def _format_long_image(self):
1726
1104
        """
1727
1105
        Break up long string value/comment into ``CONTINUE`` cards.
1728
1106
        This is a primitive implementation: it will put the value
1731
1109
        not look pretty.
1732
1110
        """
1733
1111
 
1734
 
        val_len = 67
1735
 
        comm_len = 64
 
1112
        if self.keyword in Card._commentary_keywords:
 
1113
            return self._format_long_commentary_image()
 
1114
 
 
1115
        value_length = 67
 
1116
        comment_length = 64
1736
1117
        output = []
1737
1118
 
1738
1119
        # do the value string
1739
 
        valfmt = "'%-s&'"
1740
 
        val = self.value.replace("'", "''")
1741
 
        val_list = self._words_group(val, val_len)
1742
 
        for idx in range(len(val_list)):
 
1120
        value_format = "'%-s&'"
 
1121
        value = self._value.replace("'", "''")
 
1122
        words = _words_group(value, value_length)
 
1123
        for idx, word in enumerate(words):
1743
1124
            if idx == 0:
1744
 
                headstr = "%-8s= " % self.key
 
1125
                headstr = '%-8s= ' % self.keyword
1745
1126
            else:
1746
 
                headstr = "CONTINUE  "
1747
 
            valstr = valfmt % val_list[idx]
1748
 
            output.append('%-80s' % (headstr + valstr))
 
1127
                headstr = 'CONTINUE  '
 
1128
            value = value_format % word
 
1129
            output.append('%-80s' % (headstr + value))
1749
1130
 
1750
1131
        # do the comment string
1751
 
        if self.comment is None:
1752
 
            comm = ''
 
1132
        comment_format = "%-s"
 
1133
        if self.comment:
 
1134
            words = _words_group(self.comment, comment_length)
 
1135
            for word in words:
 
1136
                comment = "CONTINUE  '&' / " + comment_format % word
 
1137
                output.append('%-80s' % comment)
 
1138
 
 
1139
        return ''.join(output)
 
1140
 
 
1141
    def _format_long_commentary_image(self):
 
1142
        """
 
1143
        If a commentary card's value is too long to fit on a single card, this
 
1144
        will render the card as multiple consecutive commentary card of the
 
1145
        same type.
 
1146
        """
 
1147
 
 
1148
        maxlen = Card.length - len(self.keyword) - 1
 
1149
        value = self._format_value()
 
1150
        output = []
 
1151
        idx = 0
 
1152
        while idx < len(value):
 
1153
            output.append(str(Card(self.keyword, value[idx:idx + maxlen])))
 
1154
            idx += maxlen
 
1155
        return ''.join(output)
 
1156
 
 
1157
    def _verify(self, option='warn'):
 
1158
        self._verified = True
 
1159
 
 
1160
        errs = _ErrList([])
 
1161
        fix_text = 'Fixed %r card to meet the FITS standard.' % self.keyword
 
1162
 
 
1163
        # Don't try to verify cards that already don't meet any recognizable
 
1164
        # standard
 
1165
        if self._invalid:
 
1166
            return errs
 
1167
 
 
1168
        # verify the equal sign position
 
1169
        if (self.keyword not in self._commentary_keywords and
 
1170
            (self._image and self._image[:8].upper() != 'HIERARCH' and
 
1171
             self._image.find('=') != 8)):
 
1172
            errs.append(self.run_option(
 
1173
                option,
 
1174
                err_text='Card %r is not FITS standard (equal sign not '
 
1175
                         'at column 8).' % self.keyword,
 
1176
                fix_text=fix_text,
 
1177
                fix=self._fix_value))
 
1178
 
 
1179
        # verify the key, it is never fixable
 
1180
        # always fix silently the case where "=" is before column 9,
 
1181
        # since there is no way to communicate back to the _keys.
 
1182
        if self._image and self._image[:8].upper() == 'HIERARCH':
 
1183
            pass
1753
1184
        else:
1754
 
            comm = self.comment
1755
 
        commfmt = "%-s"
1756
 
        if not comm == '':
1757
 
            comm_list = self._words_group(comm, comm_len)
1758
 
            for idx in comm_list:
1759
 
                commstr = "CONTINUE  '&' / " + commfmt % idx
1760
 
                output.append('%-80s' % commstr)
1761
 
 
1762
 
        return ''.join(output)
1763
 
 
1764
 
    def _words_group(self, input, strlen):
1765
 
        """
1766
 
        Split a long string into parts where each part is no longer
1767
 
        than `strlen` and no word is cut into two pieces.  But if
1768
 
        there is one single word which is longer than `strlen`, then
1769
 
        it will be split in the middle of the word.
1770
 
        """
1771
 
 
1772
 
        lst = []
1773
 
        _nblanks = input.count(' ')
1774
 
        nmax = max(_nblanks, len(input)//strlen+1)
1775
 
        arr = np.fromstring((input + ' '), dtype=(np.bytes_, 1))
1776
 
 
1777
 
        # locations of the blanks
1778
 
        blank_loc = np.nonzero(arr == np.bytes_(' '))[0]
1779
 
        offset = 0
1780
 
        xoffset = 0
1781
 
        for idx in range(nmax):
1782
 
            try:
1783
 
                loc = np.nonzero(blank_loc >= strlen+offset)[0][0]
1784
 
                offset = blank_loc[loc-1] + 1
1785
 
                if loc == 0:
1786
 
                    offset = -1
1787
 
            except:
1788
 
                offset = len(input)
1789
 
 
1790
 
            # check for one word longer than strlen, break in the middle
1791
 
            if offset <= xoffset:
1792
 
                offset = xoffset + strlen
1793
 
 
1794
 
            # collect the pieces in a list
1795
 
            tmp = input[xoffset:offset]
1796
 
            lst.append(tmp)
1797
 
            if len(input) == offset:
1798
 
                break
1799
 
            xoffset = offset
1800
 
 
1801
 
        return lst
1802
 
 
1803
 
 
1804
 
def _value_to_string(value):
1805
 
    """Converts a card value to its appropriate string representation as
 
1185
            keyword = self.keyword
 
1186
            if self.field_specifier:
 
1187
                keyword = keyword.split('.', 1)[0]
 
1188
 
 
1189
            if keyword != keyword.upper():
 
1190
            # Keyword should be uppercase unless it's a HIERARCH card
 
1191
                errs.append(self.run_option(
 
1192
                    option,
 
1193
                    err_text='Card keyword %r is not upper case.' % keyword,
 
1194
                    fix_text=fix_text,
 
1195
                    fix=self._fix_keyword))
 
1196
            elif not self._keywd_FSC_RE.match(keyword):
 
1197
                errs.append(self.run_option(
 
1198
                    option,
 
1199
                    err_text='Illegal keyword name %s' % repr(keyword),
 
1200
                    fixable=False))
 
1201
 
 
1202
        # verify the value, it may be fixable
 
1203
        keyword, valuecomment = self._split()
 
1204
        m = self._value_FSC_RE.match(valuecomment)
 
1205
        if not (m or self.keyword in self._commentary_keywords):
 
1206
            errs.append(self.run_option(
 
1207
                option,
 
1208
                err_text='Card %r is not FITS standard (invalid value '
 
1209
                         'string: %s).' % (self.keyword, valuecomment),
 
1210
                fix_text=fix_text,
 
1211
                fix=self._fix_value))
 
1212
 
 
1213
        # verify the comment (string), it is never fixable
 
1214
        m = self._value_NFSC_RE.match(valuecomment)
 
1215
        if m is not None:
 
1216
            comment = m.group('comm')
 
1217
            if comment is not None:
 
1218
                if not self._comment_FSC_RE.match(comment):
 
1219
                    errs.append(self.run_option(
 
1220
                        option,
 
1221
                        err_text='Unprintable string %r' % comment,
 
1222
                        fixable=False))
 
1223
 
 
1224
        return errs
 
1225
 
 
1226
    def _itersubcards(self):
 
1227
        """
 
1228
        If the card image is greater than 80 characters, it should consist of a
 
1229
        normal card followed by one or more CONTINUE card.  This method returns
 
1230
        the subcards that make up this logical card.
 
1231
        """
 
1232
 
 
1233
        ncards = len(self._image) // Card.length
 
1234
 
 
1235
        for idx in xrange(0, Card.length * ncards, Card.length):
 
1236
            card = Card.fromstring(self._image[idx:idx + Card.length])
 
1237
            if idx > 0 and card.keyword.upper() != 'CONTINUE':
 
1238
                raise VerifyError(
 
1239
                        'Long card images must have CONTINUE cards after '
 
1240
                        'the first card.')
 
1241
 
 
1242
            if not isinstance(card.value, str):
 
1243
                raise VerifyError('CONTINUE cards must have string values.')
 
1244
 
 
1245
            yield card
 
1246
 
 
1247
 
 
1248
def create_card(key='', value='', comment=''):
 
1249
    return Card(key, value, comment)
 
1250
create_card.__doc__ = Card.__init__.__doc__
 
1251
# For API backwards-compatibility
 
1252
createCard = deprecated('3.0', name='createCard',
 
1253
                        alternative=':meth:`Card.__init__`')(create_card)
 
1254
create_card = deprecated('3.1', name='create_card',
 
1255
                         alternative=':meth:`Card.__init__`',
 
1256
                         pending=True)(create_card)
 
1257
 
 
1258
 
 
1259
def create_card_from_string(input):
 
1260
    return Card.fromstring(input)
 
1261
create_card_from_string.__doc__ = Card.fromstring.__doc__
 
1262
# For API backwards-compat
 
1263
createCardFromString = \
 
1264
    deprecated('3.0', name='createCardFromString',
 
1265
               alternative=':meth:`Card.fromstring`')(create_card_from_string)
 
1266
create_card_from_string = deprecated('3.1', name='create_card_from_string',
 
1267
                                     alternative=':meth:`Card.fromstring`',
 
1268
                                     pending=True)(create_card_from_string)
 
1269
 
 
1270
 
 
1271
def upper_key(key):
 
1272
    return Card.normalize_keyword(key)
 
1273
upper_key.__doc__ = Card.normalize_keyword.__doc__
 
1274
# For API backwards-compat
 
1275
upperKey = deprecated('3.0', name='upperKey',
 
1276
                      alternative=':meth:`Card.normalize_keyword`')(upper_key)
 
1277
upper_key = deprecated('3.1', name='upper_key',
 
1278
                       alternative=':meth:`Card.normalize_keyword`',
 
1279
                       pending=True)(upper_key)
 
1280
 
 
1281
 
 
1282
def _int_or_float(s):
 
1283
    """
 
1284
    Converts an a string to an int if possible, or to a float.
 
1285
 
 
1286
    If the string is neither a string or a float a value error is raised.
 
1287
    """
 
1288
 
 
1289
    if isinstance(s, float):
 
1290
        # Already a float so just pass through
 
1291
        return s
 
1292
 
 
1293
    try:
 
1294
        return int(s)
 
1295
    except (ValueError, TypeError):
 
1296
        try:
 
1297
            return float(s)
 
1298
        except (ValueError, TypeError), e:
 
1299
            raise ValueError(str(e))
 
1300
 
 
1301
 
 
1302
def _format_value(value):
 
1303
    """
 
1304
    Converts a card value to its appropriate string representation as
1806
1305
    defined by the FITS format.
1807
1306
    """
1808
1307
 
1809
1308
    # string value should occupies at least 8 columns, unless it is
1810
1309
    # a null string
1811
 
    if isinstance(value, str):
 
1310
    if isinstance(value, basestring):
1812
1311
        if value == '':
1813
1312
            return "''"
1814
1313
        else:
1818
1317
 
1819
1318
    # must be before int checking since bool is also int
1820
1319
    elif isinstance(value, (bool, np.bool_)):
1821
 
        return '%20s' % repr(value)[0] # T or F
 
1320
        return '%20s' % repr(value)[0]  # T or F
1822
1321
 
1823
1322
    elif _is_int(value):
1824
1323
        return '%20d' % value
1825
1324
 
1826
 
    # XXX need to consider platform dependence of the format (e.g. E-009 vs. E-09)
1827
1325
    elif isinstance(value, (float, np.floating)):
1828
 
        return '%20s' % _float_format(value)
 
1326
        return '%20s' % _format_float(value)
1829
1327
 
1830
1328
    elif isinstance(value, (complex, np.complexfloating)):
1831
 
        val_str = '(%s, %s)' % (_float_format(value.real),
1832
 
                                _float_format(value.imag))
 
1329
        val_str = '(%s, %s)' % (_format_float(value.real),
 
1330
                                _format_float(value.imag))
1833
1331
        return '%20s' % val_str
1834
1332
 
1835
1333
    elif isinstance(value, Undefined):
1838
1336
        return ''
1839
1337
 
1840
1338
 
1841
 
def _float_format(value):
 
1339
def _format_float(value):
1842
1340
    """Format a floating number to make sure it gets the decimal point."""
1843
1341
 
1844
1342
    value_str = '%.16G' % value
1865
1363
        if idx < 0:
1866
1364
            value_str = value_str[:20]
1867
1365
        else:
1868
 
            value_str = value_str[:20-(str_len-idx)] + value_str[idx:]
 
1366
            value_str = value_str[:20 - (str_len - idx)] + value_str[idx:]
1869
1367
 
1870
1368
    return value_str
1871
1369
 
1881
1379
        if strlen == 0:
1882
1380
            return input
1883
1381
        else:
1884
 
            return input + ' ' * (Card.length-strlen)
 
1382
            return input + ' ' * (Card.length - strlen)
1885
1383
 
1886
1384
    # minimum length is 80
1887
1385
    else:
1888
1386
        strlen = _len % Card.length
1889
 
        return input + ' ' * (Card.length-strlen)
1890
 
 
1891
 
 
 
1387
        return input + ' ' * (Card.length - strlen)