1# Licensed under a 3-clause BSD style license - see PYFITS.rst
2
3import collections
4import copy
5import itertools
6import numbers
7import re
8import warnings
9
10from .card import Card, _pad, KEYWORD_LENGTH, UNDEFINED
11from .file import _File
12from .util import (encode_ascii, decode_ascii, fileobj_closed,
13                   fileobj_is_binary, path_like)
14from ._utils import parse_header
15
16from astropy.utils import isiterable
17from astropy.utils.exceptions import AstropyUserWarning
18from astropy.utils.decorators import deprecated_renamed_argument
19
20
21BLOCK_SIZE = 2880  # the FITS block size
22
23# This regular expression can match a *valid* END card which just consists of
24# the string 'END' followed by all spaces, or an *invalid* end card which
25# consists of END, followed by any character that is *not* a valid character
26# for a valid FITS keyword (that is, this is not a keyword like 'ENDER' which
27# starts with 'END' but is not 'END'), followed by any arbitrary bytes.  An
28# invalid end card may also consist of just 'END' with no trailing bytes.
29HEADER_END_RE = re.compile(encode_ascii(
30    r'(?:(?P<valid>END {77}) *)|(?P<invalid>END$|END {0,76}[^A-Z0-9_-])'))
31
32
33# According to the FITS standard the only characters that may appear in a
34# header record are the restricted ASCII chars from 0x20 through 0x7E.
35VALID_HEADER_CHARS = set(map(chr, range(0x20, 0x7F)))
36END_CARD = 'END' + ' ' * 77
37
38
39__doctest_skip__ = ['Header', 'Header.comments', 'Header.fromtextfile',
40                    'Header.totextfile', 'Header.set', 'Header.update']
41
42
43class Header:
44    """
45    FITS header class.  This class exposes both a dict-like interface and a
46    list-like interface to FITS headers.
47
48    The header may be indexed by keyword and, like a dict, the associated value
49    will be returned.  When the header contains cards with duplicate keywords,
50    only the value of the first card with the given keyword will be returned.
51    It is also possible to use a 2-tuple as the index in the form (keyword,
52    n)--this returns the n-th value with that keyword, in the case where there
53    are duplicate keywords.
54
55    For example::
56
57        >>> header['NAXIS']
58        0
59        >>> header[('FOO', 1)]  # Return the value of the second FOO keyword
60        'foo'
61
62    The header may also be indexed by card number::
63
64        >>> header[0]  # Return the value of the first card in the header
65        'T'
66
67    Commentary keywords such as HISTORY and COMMENT are special cases: When
68    indexing the Header object with either 'HISTORY' or 'COMMENT' a list of all
69    the HISTORY/COMMENT values is returned::
70
71        >>> header['HISTORY']
72        This is the first history entry in this header.
73        This is the second history entry in this header.
74        ...
75
76    See the Astropy documentation for more details on working with headers.
77
78    Notes
79    -----
80    Although FITS keywords must be exclusively upper case, retrieving an item
81    in a `Header` object is case insensitive.
82    """
83
84    def __init__(self, cards=[], copy=False):
85        """
86        Construct a `Header` from an iterable and/or text file.
87
88        Parameters
89        ----------
90        cards : list of `Card`, optional
91            The cards to initialize the header with. Also allowed are other
92            `Header` (or `dict`-like) objects.
93
94            .. versionchanged:: 1.2
95                Allowed ``cards`` to be a `dict`-like object.
96
97        copy : bool, optional
98
99            If ``True`` copies the ``cards`` if they were another `Header`
100            instance.
101            Default is ``False``.
102
103            .. versionadded:: 1.3
104        """
105        self.clear()
106
107        if isinstance(cards, Header):
108            if copy:
109                cards = cards.copy()
110            cards = cards.cards
111        elif isinstance(cards, dict):
112            cards = cards.items()
113
114        for card in cards:
115            self.append(card, end=True)
116
117        self._modified = False
118
119    def __len__(self):
120        return len(self._cards)
121
122    def __iter__(self):
123        for card in self._cards:
124            yield card.keyword
125
126    def __contains__(self, keyword):
127        if keyword in self._keyword_indices or keyword in self._rvkc_indices:
128            # For the most common case (single, standard form keyword lookup)
129            # this will work and is an O(1) check.  If it fails that doesn't
130            # guarantee absence, just that we have to perform the full set of
131            # checks in self._cardindex
132            return True
133        try:
134            self._cardindex(keyword)
135        except (KeyError, IndexError):
136            return False
137        return True
138
139    def __getitem__(self, key):
140        if isinstance(key, slice):
141            return self.__class__([copy.copy(c) for c in self._cards[key]])
142        elif self._haswildcard(key):
143            return self.__class__([copy.copy(self._cards[idx])
144                                   for idx in self._wildcardmatch(key)])
145        elif isinstance(key, str):
146            key = key.strip()
147            if key.upper() in Card._commentary_keywords:
148                key = key.upper()
149                # Special case for commentary cards
150                return _HeaderCommentaryCards(self, key)
151
152        if isinstance(key, tuple):
153            keyword = key[0]
154        else:
155            keyword = key
156
157        card = self._cards[self._cardindex(key)]
158
159        if card.field_specifier is not None and keyword == card.rawkeyword:
160            # This is RVKC; if only the top-level keyword was specified return
161            # the raw value, not the parsed out float value
162            return card.rawvalue
163
164        value = card.value
165        if value == UNDEFINED:
166            return None
167        return value
168
169    def __setitem__(self, key, value):
170        if self._set_slice(key, value, self):
171            return
172
173        if isinstance(value, tuple):
174            if len(value) > 2:
175                raise ValueError(
176                    'A Header item may be set with either a scalar value, '
177                    'a 1-tuple containing a scalar value, or a 2-tuple '
178                    'containing a scalar value and comment string.')
179            if len(value) == 1:
180                value, comment = value[0], None
181                if value is None:
182                    value = UNDEFINED
183            elif len(value) == 2:
184                value, comment = value
185                if value is None:
186                    value = UNDEFINED
187                if comment is None:
188                    comment = ''
189        else:
190            comment = None
191
192        card = None
193        if isinstance(key, numbers.Integral):
194            card = self._cards[key]
195        elif isinstance(key, tuple):
196            card = self._cards[self._cardindex(key)]
197        if value is None:
198            value = UNDEFINED
199        if card:
200            card.value = value
201            if comment is not None:
202                card.comment = comment
203            if card._modified:
204                self._modified = True
205        else:
206            # If we get an IndexError that should be raised; we don't allow
207            # assignment to non-existing indices
208            self._update((key, value, comment))
209
210    def __delitem__(self, key):
211        if isinstance(key, slice) or self._haswildcard(key):
212            # This is very inefficient but it's not a commonly used feature.
213            # If someone out there complains that they make heavy use of slice
214            # deletions and it's too slow, well, we can worry about it then
215            # [the solution is not too complicated--it would be wait 'til all
216            # the cards are deleted before updating _keyword_indices rather
217            # than updating it once for each card that gets deleted]
218            if isinstance(key, slice):
219                indices = range(*key.indices(len(self)))
220                # If the slice step is backwards we want to reverse it, because
221                # it will be reversed in a few lines...
222                if key.step and key.step < 0:
223                    indices = reversed(indices)
224            else:
225                indices = self._wildcardmatch(key)
226            for idx in reversed(indices):
227                del self[idx]
228            return
229        elif isinstance(key, str):
230            # delete ALL cards with the same keyword name
231            key = Card.normalize_keyword(key)
232            indices = self._keyword_indices
233            if key not in self._keyword_indices:
234                indices = self._rvkc_indices
235
236            if key not in indices:
237                # if keyword is not present raise KeyError.
238                # To delete keyword without caring if they were present,
239                # Header.remove(Keyword) can be used with optional argument ignore_missing as True
240                raise KeyError(f"Keyword '{key}' not found.")
241
242            for idx in reversed(indices[key]):
243                # Have to copy the indices list since it will be modified below
244                del self[idx]
245            return
246
247        idx = self._cardindex(key)
248        card = self._cards[idx]
249        keyword = card.keyword
250        del self._cards[idx]
251        keyword = Card.normalize_keyword(keyword)
252        indices = self._keyword_indices[keyword]
253        indices.remove(idx)
254        if not indices:
255            del self._keyword_indices[keyword]
256
257        # Also update RVKC indices if necessary :/
258        if card.field_specifier is not None:
259            indices = self._rvkc_indices[card.rawkeyword]
260            indices.remove(idx)
261            if not indices:
262                del self._rvkc_indices[card.rawkeyword]
263
264        # We also need to update all other indices
265        self._updateindices(idx, increment=False)
266        self._modified = True
267
268    def __repr__(self):
269        return self.tostring(sep='\n', endcard=False, padding=False)
270
271    def __str__(self):
272        return self.tostring()
273
274    def __eq__(self, other):
275        """
276        Two Headers are equal only if they have the exact same string
277        representation.
278        """
279
280        return str(self) == str(other)
281
282    def __add__(self, other):
283        temp = self.copy(strip=False)
284        temp.extend(other)
285        return temp
286
287    def __iadd__(self, other):
288        self.extend(other)
289        return self
290
291    def _ipython_key_completions_(self):
292        return self.__iter__()
293
294    @property
295    def cards(self):
296        """
297        The underlying physical cards that make up this Header; it can be
298        looked at, but it should not be modified directly.
299        """
300
301        return _CardAccessor(self)
302
303    @property
304    def comments(self):
305        """
306        View the comments associated with each keyword, if any.
307
308        For example, to see the comment on the NAXIS keyword:
309
310            >>> header.comments['NAXIS']
311            number of data axes
312
313        Comments can also be updated through this interface:
314
315            >>> header.comments['NAXIS'] = 'Number of data axes'
316
317        """
318
319        return _HeaderComments(self)
320
321    @property
322    def _modified(self):
323        """
324        Whether or not the header has been modified; this is a property so that
325        it can also check each card for modifications--cards may have been
326        modified directly without the header containing it otherwise knowing.
327        """
328
329        modified_cards = any(c._modified for c in self._cards)
330        if modified_cards:
331            # If any cards were modified then by definition the header was
332            # modified
333            self.__dict__['_modified'] = True
334
335        return self.__dict__['_modified']
336
337    @_modified.setter
338    def _modified(self, val):
339        self.__dict__['_modified'] = val
340
341    @classmethod
342    def fromstring(cls, data, sep=''):
343        """
344        Creates an HDU header from a byte string containing the entire header
345        data.
346
347        Parameters
348        ----------
349        data : str or bytes
350           String or bytes containing the entire header.  In the case of bytes
351           they will be decoded using latin-1 (only plain ASCII characters are
352           allowed in FITS headers but latin-1 allows us to retain any invalid
353           bytes that might appear in malformatted FITS files).
354
355        sep : str, optional
356            The string separating cards from each other, such as a newline.  By
357            default there is no card separator (as is the case in a raw FITS
358            file).  In general this is only used in cases where a header was
359            printed as text (e.g. with newlines after each card) and you want
360            to create a new `Header` from it by copy/pasting.
361
362        Examples
363        --------
364
365        >>> from astropy.io.fits import Header
366        >>> hdr = Header({'SIMPLE': True})
367        >>> Header.fromstring(hdr.tostring()) == hdr
368        True
369
370        If you want to create a `Header` from printed text it's not necessary
371        to have the exact binary structure as it would appear in a FITS file,
372        with the full 80 byte card length.  Rather, each "card" can end in a
373        newline and does not have to be padded out to a full card length as
374        long as it "looks like" a FITS header:
375
376        >>> hdr = Header.fromstring(\"\"\"\\
377        ... SIMPLE  =                    T / conforms to FITS standard
378        ... BITPIX  =                    8 / array data type
379        ... NAXIS   =                    0 / number of array dimensions
380        ... EXTEND  =                    T
381        ... \"\"\", sep='\\n')
382        >>> hdr['SIMPLE']
383        True
384        >>> hdr['BITPIX']
385        8
386        >>> len(hdr)
387        4
388
389        Returns
390        -------
391        `Header`
392            A new `Header` instance.
393        """
394
395        cards = []
396
397        # If the card separator contains characters that may validly appear in
398        # a card, the only way to unambiguously distinguish between cards is to
399        # require that they be Card.length long.  However, if the separator
400        # contains non-valid characters (namely \n) the cards may be split
401        # immediately at the separator
402        require_full_cardlength = set(sep).issubset(VALID_HEADER_CHARS)
403
404        if isinstance(data, bytes):
405            # FITS supports only ASCII, but decode as latin1 and just take all
406            # bytes for now; if it results in mojibake due to e.g. UTF-8
407            # encoded data in a FITS header that's OK because it shouldn't be
408            # there in the first place--accepting it here still gives us the
409            # opportunity to display warnings later during validation
410            CONTINUE = b'CONTINUE'
411            END = b'END'
412            end_card = END_CARD.encode('ascii')
413            sep = sep.encode('latin1')
414            empty = b''
415        else:
416            CONTINUE = 'CONTINUE'
417            END = 'END'
418            end_card = END_CARD
419            empty = ''
420
421        # Split the header into individual cards
422        idx = 0
423        image = []
424
425        while idx < len(data):
426            if require_full_cardlength:
427                end_idx = idx + Card.length
428            else:
429                try:
430                    end_idx = data.index(sep, idx)
431                except ValueError:
432                    end_idx = len(data)
433
434            next_image = data[idx:end_idx]
435            idx = end_idx + len(sep)
436
437            if image:
438                if next_image[:8] == CONTINUE:
439                    image.append(next_image)
440                    continue
441                cards.append(Card.fromstring(empty.join(image)))
442
443            if require_full_cardlength:
444                if next_image == end_card:
445                    image = []
446                    break
447            else:
448                if next_image.split(sep)[0].rstrip() == END:
449                    image = []
450                    break
451
452            image = [next_image]
453
454        # Add the last image that was found before the end, if any
455        if image:
456            cards.append(Card.fromstring(empty.join(image)))
457
458        return cls._fromcards(cards)
459
460    @classmethod
461    def fromfile(cls, fileobj, sep='', endcard=True, padding=True):
462        """
463        Similar to :meth:`Header.fromstring`, but reads the header string from
464        a given file-like object or filename.
465
466        Parameters
467        ----------
468        fileobj : str, file-like
469            A filename or an open file-like object from which a FITS header is
470            to be read.  For open file handles the file pointer must be at the
471            beginning of the header.
472
473        sep : str, optional
474            The string separating cards from each other, such as a newline.  By
475            default there is no card separator (as is the case in a raw FITS
476            file).
477
478        endcard : bool, optional
479            If True (the default) the header must end with an END card in order
480            to be considered valid.  If an END card is not found an
481            `OSError` is raised.
482
483        padding : bool, optional
484            If True (the default) the header will be required to be padded out
485            to a multiple of 2880, the FITS header block size.  Otherwise any
486            padding, or lack thereof, is ignored.
487
488        Returns
489        -------
490        `Header`
491            A new `Header` instance.
492        """
493
494        close_file = False
495
496        if isinstance(fileobj, path_like):
497            # If sep is non-empty we are trying to read a header printed to a
498            # text file, so open in text mode by default to support newline
499            # handling; if a binary-mode file object is passed in, the user is
500            # then on their own w.r.t. newline handling.
501            #
502            # Otherwise assume we are reading from an actual FITS file and open
503            # in binary mode.
504            if sep:
505                fileobj = open(fileobj, 'r', encoding='latin1')
506            else:
507                fileobj = open(fileobj, 'rb')
508
509            close_file = True
510
511        try:
512            is_binary = fileobj_is_binary(fileobj)
513
514            def block_iter(nbytes):
515                while True:
516                    data = fileobj.read(nbytes)
517
518                    if data:
519                        yield data
520                    else:
521                        break
522
523            return cls._from_blocks(block_iter, is_binary, sep, endcard,
524                                    padding)[1]
525        finally:
526            if close_file:
527                fileobj.close()
528
529    @classmethod
530    def _fromcards(cls, cards):
531        header = cls()
532        for idx, card in enumerate(cards):
533            header._cards.append(card)
534            keyword = Card.normalize_keyword(card.keyword)
535            header._keyword_indices[keyword].append(idx)
536            if card.field_specifier is not None:
537                header._rvkc_indices[card.rawkeyword].append(idx)
538
539        header._modified = False
540        return header
541
542    @classmethod
543    def _from_blocks(cls, block_iter, is_binary, sep, endcard, padding):
544        """
545        The meat of `Header.fromfile`; in a separate method so that
546        `Header.fromfile` itself is just responsible for wrapping file
547        handling.  Also used by `_BaseHDU.fromstring`.
548
549        ``block_iter`` should be a callable which, given a block size n
550        (typically 2880 bytes as used by the FITS standard) returns an iterator
551        of byte strings of that block size.
552
553        ``is_binary`` specifies whether the returned blocks are bytes or text
554
555        Returns both the entire header *string*, and the `Header` object
556        returned by Header.fromstring on that string.
557        """
558
559        actual_block_size = _block_size(sep)
560        clen = Card.length + len(sep)
561
562        blocks = block_iter(actual_block_size)
563
564        # Read the first header block.
565        try:
566            block = next(blocks)
567        except StopIteration:
568            raise EOFError()
569
570        if not is_binary:
571            # TODO: There needs to be error handling at *this* level for
572            # non-ASCII characters; maybe at this stage decoding latin-1 might
573            # be safer
574            block = encode_ascii(block)
575
576        read_blocks = []
577        is_eof = False
578        end_found = False
579
580        # continue reading header blocks until END card or EOF is reached
581        while True:
582            # find the END card
583            end_found, block = cls._find_end_card(block, clen)
584
585            read_blocks.append(decode_ascii(block))
586
587            if end_found:
588                break
589
590            try:
591                block = next(blocks)
592            except StopIteration:
593                is_eof = True
594                break
595
596            if not block:
597                is_eof = True
598                break
599
600            if not is_binary:
601                block = encode_ascii(block)
602
603        header_str = ''.join(read_blocks)
604        _check_padding(header_str, actual_block_size, is_eof,
605                       check_block_size=padding)
606
607        if not end_found and is_eof and endcard:
608            # TODO: Pass this error to validation framework as an ERROR,
609            # rather than raising an exception
610            raise OSError('Header missing END card.')
611
612        return header_str, cls.fromstring(header_str, sep=sep)
613
614    @classmethod
615    def _find_end_card(cls, block, card_len):
616        """
617        Utility method to search a header block for the END card and handle
618        invalid END cards.
619
620        This method can also returned a modified copy of the input header block
621        in case an invalid end card needs to be sanitized.
622        """
623
624        for mo in HEADER_END_RE.finditer(block):
625            # Ensure the END card was found, and it started on the
626            # boundary of a new card (see ticket #142)
627            if mo.start() % card_len != 0:
628                continue
629
630            # This must be the last header block, otherwise the
631            # file is malformatted
632            if mo.group('invalid'):
633                offset = mo.start()
634                trailing = block[offset + 3:offset + card_len - 3].rstrip()
635                if trailing:
636                    trailing = repr(trailing).lstrip('ub')
637                    # TODO: Pass this warning up to the validation framework
638                    warnings.warn(
639                        'Unexpected bytes trailing END keyword: {}; these '
640                        'bytes will be replaced with spaces on write.'.format(
641                            trailing), AstropyUserWarning)
642                else:
643                    # TODO: Pass this warning up to the validation framework
644                    warnings.warn(
645                        'Missing padding to end of the FITS block after the '
646                        'END keyword; additional spaces will be appended to '
647                        'the file upon writing to pad out to {} '
648                        'bytes.'.format(BLOCK_SIZE), AstropyUserWarning)
649
650                # Sanitize out invalid END card now that the appropriate
651                # warnings have been issued
652                block = (block[:offset] + encode_ascii(END_CARD) +
653                         block[offset + len(END_CARD):])
654
655            return True, block
656
657        return False, block
658
659    def tostring(self, sep='', endcard=True, padding=True):
660        r"""
661        Returns a string representation of the header.
662
663        By default this uses no separator between cards, adds the END card, and
664        pads the string with spaces to the next multiple of 2880 bytes.  That
665        is, it returns the header exactly as it would appear in a FITS file.
666
667        Parameters
668        ----------
669        sep : str, optional
670            The character or string with which to separate cards.  By default
671            there is no separator, but one could use ``'\\n'``, for example, to
672            separate each card with a new line
673
674        endcard : bool, optional
675            If True (default) adds the END card to the end of the header
676            string
677
678        padding : bool, optional
679            If True (default) pads the string with spaces out to the next
680            multiple of 2880 characters
681
682        Returns
683        -------
684        str
685            A string representing a FITS header.
686        """
687
688        lines = []
689        for card in self._cards:
690            s = str(card)
691            # Cards with CONTINUE cards may be longer than 80 chars; so break
692            # them into multiple lines
693            while s:
694                lines.append(s[:Card.length])
695                s = s[Card.length:]
696
697        s = sep.join(lines)
698        if endcard:
699            s += sep + _pad('END')
700        if padding:
701            s += ' ' * _pad_length(len(s))
702        return s
703
704    @deprecated_renamed_argument('clobber', 'overwrite', '2.0',
705                                 message='"clobber" was deprecated in version '
706                                         '2.0 and will be removed in version '
707                                         '5.1. Use argument "overwrite" '
708                                         'instead.')
709    def tofile(self, fileobj, sep='', endcard=True, padding=True,
710               overwrite=False):
711        r"""
712        Writes the header to file or file-like object.
713
714        By default this writes the header exactly as it would be written to a
715        FITS file, with the END card included and padding to the next multiple
716        of 2880 bytes.  However, aspects of this may be controlled.
717
718        Parameters
719        ----------
720        fileobj : path-like or file-like, optional
721            Either the pathname of a file, or an open file handle or file-like
722            object.
723
724        sep : str, optional
725            The character or string with which to separate cards.  By default
726            there is no separator, but one could use ``'\\n'``, for example, to
727            separate each card with a new line
728
729        endcard : bool, optional
730            If `True` (default) adds the END card to the end of the header
731            string
732
733        padding : bool, optional
734            If `True` (default) pads the string with spaces out to the next
735            multiple of 2880 characters
736
737        overwrite : bool, optional
738            If ``True``, overwrite the output file if it exists. Raises an
739            ``OSError`` if ``False`` and the output file exists. Default is
740            ``False``.
741
742            .. versionchanged:: 1.3
743               ``overwrite`` replaces the deprecated ``clobber`` argument.
744        """
745
746        close_file = fileobj_closed(fileobj)
747
748        if not isinstance(fileobj, _File):
749            fileobj = _File(fileobj, mode='ostream', overwrite=overwrite)
750
751        try:
752            blocks = self.tostring(sep=sep, endcard=endcard, padding=padding)
753            actual_block_size = _block_size(sep)
754            if padding and len(blocks) % actual_block_size != 0:
755                raise OSError(
756                    'Header size ({}) is not a multiple of block '
757                    'size ({}).'.format(
758                        len(blocks) - actual_block_size + BLOCK_SIZE,
759                        BLOCK_SIZE))
760
761            fileobj.flush()
762            fileobj.write(blocks.encode('ascii'))
763            fileobj.flush()
764        finally:
765            if close_file:
766                fileobj.close()
767
768    @classmethod
769    def fromtextfile(cls, fileobj, endcard=False):
770        """
771        Read a header from a simple text file or file-like object.
772
773        Equivalent to::
774
775            >>> Header.fromfile(fileobj, sep='\\n', endcard=False,
776            ...                 padding=False)
777
778        See Also
779        --------
780        fromfile
781        """
782
783        return cls.fromfile(fileobj, sep='\n', endcard=endcard, padding=False)
784
785    @deprecated_renamed_argument('clobber', 'overwrite', '2.0',
786                                 message='"clobber" was deprecated in version '
787                                         '2.0 and will be removed in version '
788                                         '5.1. Use argument "overwrite" '
789                                         'instead.')
790    def totextfile(self, fileobj, endcard=False, overwrite=False):
791        """
792        Write the header as text to a file or a file-like object.
793
794        Equivalent to::
795
796            >>> Header.tofile(fileobj, sep='\\n', endcard=False,
797            ...               padding=False, overwrite=overwrite)
798
799        .. versionchanged:: 1.3
800           ``overwrite`` replaces the deprecated ``clobber`` argument.
801
802        See Also
803        --------
804        tofile
805        """
806
807        self.tofile(fileobj, sep='\n', endcard=endcard, padding=False,
808                    overwrite=overwrite)
809
810    def clear(self):
811        """
812        Remove all cards from the header.
813        """
814
815        self._cards = []
816        self._keyword_indices = collections.defaultdict(list)
817        self._rvkc_indices = collections.defaultdict(list)
818
819    def copy(self, strip=False):
820        """
821        Make a copy of the :class:`Header`.
822
823        .. versionchanged:: 1.3
824            `copy.copy` and `copy.deepcopy` on a `Header` will call this
825            method.
826
827        Parameters
828        ----------
829        strip : bool, optional
830           If `True`, strip any headers that are specific to one of the
831           standard HDU types, so that this header can be used in a different
832           HDU.
833
834        Returns
835        -------
836        `Header`
837            A new :class:`Header` instance.
838        """
839
840        tmp = self.__class__((copy.copy(card) for card in self._cards))
841        if strip:
842            tmp.strip()
843        return tmp
844
845    def __copy__(self):
846        return self.copy()
847
848    def __deepcopy__(self, *args, **kwargs):
849        return self.copy()
850
851    @classmethod
852    def fromkeys(cls, iterable, value=None):
853        """
854        Similar to :meth:`dict.fromkeys`--creates a new `Header` from an
855        iterable of keywords and an optional default value.
856
857        This method is not likely to be particularly useful for creating real
858        world FITS headers, but it is useful for testing.
859
860        Parameters
861        ----------
862        iterable
863            Any iterable that returns strings representing FITS keywords.
864
865        value : optional
866            A default value to assign to each keyword; must be a valid type for
867            FITS keywords.
868
869        Returns
870        -------
871        `Header`
872            A new `Header` instance.
873        """
874
875        d = cls()
876        if not isinstance(value, tuple):
877            value = (value,)
878        for key in iterable:
879            d.append((key,) + value)
880        return d
881
882    def get(self, key, default=None):
883        """
884        Similar to :meth:`dict.get`--returns the value associated with keyword
885        in the header, or a default value if the keyword is not found.
886
887        Parameters
888        ----------
889        key : str
890            A keyword that may or may not be in the header.
891
892        default : optional
893            A default value to return if the keyword is not found in the
894            header.
895
896        Returns
897        -------
898        value: str, number, complex, bool, or ``astropy.io.fits.card.Undefined``
899            The value associated with the given keyword, or the default value
900            if the keyword is not in the header.
901        """
902
903        try:
904            return self[key]
905        except (KeyError, IndexError):
906            return default
907
908    def set(self, keyword, value=None, comment=None, before=None, after=None):
909        """
910        Set the value and/or comment and/or position of a specified keyword.
911
912        If the keyword does not already exist in the header, a new keyword is
913        created in the specified position, or appended to the end of the header
914        if no position is specified.
915
916        This method is similar to :meth:`Header.update` prior to Astropy v0.1.
917
918        .. note::
919            It should be noted that ``header.set(keyword, value)`` and
920            ``header.set(keyword, value, comment)`` are equivalent to
921            ``header[keyword] = value`` and
922            ``header[keyword] = (value, comment)`` respectively.
923
924            New keywords can also be inserted relative to existing keywords
925            using, for example::
926
927                >>> header.insert('NAXIS1', ('NAXIS', 2, 'Number of axes'))
928
929            to insert before an existing keyword, or::
930
931                >>> header.insert('NAXIS', ('NAXIS1', 4096), after=True)
932
933            to insert after an existing keyword.
934
935            The only advantage of using :meth:`Header.set` is that it
936            easily replaces the old usage of :meth:`Header.update` both
937            conceptually and in terms of function signature.
938
939        Parameters
940        ----------
941        keyword : str
942            A header keyword
943
944        value : str, optional
945            The value to set for the given keyword; if None the existing value
946            is kept, but '' may be used to set a blank value
947
948        comment : str, optional
949            The comment to set for the given keyword; if None the existing
950            comment is kept, but ``''`` may be used to set a blank comment
951
952        before : str, int, optional
953            Name of the keyword, or index of the `Card` before which this card
954            should be located in the header.  The argument ``before`` takes
955            precedence over ``after`` if both specified.
956
957        after : str, int, optional
958            Name of the keyword, or index of the `Card` after which this card
959            should be located in the header.
960
961        """
962
963        # Create a temporary card that looks like the one being set; if the
964        # temporary card turns out to be a RVKC this will make it easier to
965        # deal with the idiosyncrasies thereof
966        # Don't try to make a temporary card though if they keyword looks like
967        # it might be a HIERARCH card or is otherwise invalid--this step is
968        # only for validating RVKCs.
969        if (len(keyword) <= KEYWORD_LENGTH and
970            Card._keywd_FSC_RE.match(keyword) and
971                keyword not in self._keyword_indices):
972            new_card = Card(keyword, value, comment)
973            new_keyword = new_card.keyword
974        else:
975            new_keyword = keyword
976
977        if (new_keyword not in Card._commentary_keywords and
978                new_keyword in self):
979            if comment is None:
980                comment = self.comments[keyword]
981            if value is None:
982                value = self[keyword]
983
984            self[keyword] = (value, comment)
985
986            if before is not None or after is not None:
987                card = self._cards[self._cardindex(keyword)]
988                self._relativeinsert(card, before=before, after=after,
989                                     replace=True)
990        elif before is not None or after is not None:
991            self._relativeinsert((keyword, value, comment), before=before,
992                                 after=after)
993        else:
994            self[keyword] = (value, comment)
995
996    def items(self):
997        """Like :meth:`dict.items`."""
998
999        for card in self._cards:
1000            yield card.keyword, None if card.value == UNDEFINED else card.value
1001
1002    def keys(self):
1003        """
1004        Like :meth:`dict.keys`--iterating directly over the `Header`
1005        instance has the same behavior.
1006        """
1007
1008        for card in self._cards:
1009            yield card.keyword
1010
1011    def values(self):
1012        """Like :meth:`dict.values`."""
1013
1014        for card in self._cards:
1015            yield None if card.value == UNDEFINED else card.value
1016
1017    def pop(self, *args):
1018        """
1019        Works like :meth:`list.pop` if no arguments or an index argument are
1020        supplied; otherwise works like :meth:`dict.pop`.
1021        """
1022
1023        if len(args) > 2:
1024            raise TypeError(f'Header.pop expected at most 2 arguments, got {len(args)}')
1025
1026        if len(args) == 0:
1027            key = -1
1028        else:
1029            key = args[0]
1030
1031        try:
1032            value = self[key]
1033        except (KeyError, IndexError):
1034            if len(args) == 2:
1035                return args[1]
1036            raise
1037
1038        del self[key]
1039        return value
1040
1041    def popitem(self):
1042        """Similar to :meth:`dict.popitem`."""
1043
1044        try:
1045            k, v = next(self.items())
1046        except StopIteration:
1047            raise KeyError('Header is empty')
1048        del self[k]
1049        return k, v
1050
1051    def setdefault(self, key, default=None):
1052        """Similar to :meth:`dict.setdefault`."""
1053
1054        try:
1055            return self[key]
1056        except (KeyError, IndexError):
1057            self[key] = default
1058        return default
1059
1060    def update(self, *args, **kwargs):
1061        """
1062        Update the Header with new keyword values, updating the values of
1063        existing keywords and appending new keywords otherwise; similar to
1064        `dict.update`.
1065
1066        `update` accepts either a dict-like object or an iterable.  In the
1067        former case the keys must be header keywords and the values may be
1068        either scalar values or (value, comment) tuples.  In the case of an
1069        iterable the items must be (keyword, value) tuples or (keyword, value,
1070        comment) tuples.
1071
1072        Arbitrary arguments are also accepted, in which case the update() is
1073        called again with the kwargs dict as its only argument.  That is,
1074
1075        ::
1076
1077            >>> header.update(NAXIS1=100, NAXIS2=100)
1078
1079        is equivalent to::
1080
1081            header.update({'NAXIS1': 100, 'NAXIS2': 100})
1082
1083        .. warning::
1084            As this method works similarly to `dict.update` it is very
1085            different from the ``Header.update()`` method in Astropy v0.1.
1086            Use of the old API was
1087            **deprecated** for a long time and is now removed. Most uses of the
1088            old API can be replaced as follows:
1089
1090            * Replace ::
1091
1092                  header.update(keyword, value)
1093
1094              with ::
1095
1096                  header[keyword] = value
1097
1098            * Replace ::
1099
1100                  header.update(keyword, value, comment=comment)
1101
1102              with ::
1103
1104                  header[keyword] = (value, comment)
1105
1106            * Replace ::
1107
1108                  header.update(keyword, value, before=before_keyword)
1109
1110              with ::
1111
1112                  header.insert(before_keyword, (keyword, value))
1113
1114            * Replace ::
1115
1116                  header.update(keyword, value, after=after_keyword)
1117
1118              with ::
1119
1120                  header.insert(after_keyword, (keyword, value),
1121                                after=True)
1122
1123            See also :meth:`Header.set` which is a new method that provides an
1124            interface similar to the old ``Header.update()`` and may help make
1125            transition a little easier.
1126
1127        """
1128
1129        if args:
1130            other = args[0]
1131        else:
1132            other = None
1133
1134        def update_from_dict(k, v):
1135            if not isinstance(v, tuple):
1136                card = Card(k, v)
1137            elif 0 < len(v) <= 2:
1138                card = Card(*((k,) + v))
1139            else:
1140                raise ValueError(
1141                    'Header update value for key %r is invalid; the '
1142                    'value must be either a scalar, a 1-tuple '
1143                    'containing the scalar value, or a 2-tuple '
1144                    'containing the value and a comment string.' % k)
1145            self._update(card)
1146
1147        if other is None:
1148            pass
1149        elif isinstance(other, Header):
1150            for card in other.cards:
1151                self._update(card)
1152        elif hasattr(other, 'items'):
1153            for k, v in other.items():
1154                update_from_dict(k, v)
1155        elif hasattr(other, 'keys'):
1156            for k in other.keys():
1157                update_from_dict(k, other[k])
1158        else:
1159            for idx, card in enumerate(other):
1160                if isinstance(card, Card):
1161                    self._update(card)
1162                elif isinstance(card, tuple) and (1 < len(card) <= 3):
1163                    self._update(Card(*card))
1164                else:
1165                    raise ValueError(
1166                        'Header update sequence item #{} is invalid; '
1167                        'the item must either be a 2-tuple containing '
1168                        'a keyword and value, or a 3-tuple containing '
1169                        'a keyword, value, and comment string.'.format(idx))
1170        if kwargs:
1171            self.update(kwargs)
1172
1173    def append(self, card=None, useblanks=True, bottom=False, end=False):
1174        """
1175        Appends a new keyword+value card to the end of the Header, similar
1176        to `list.append`.
1177
1178        By default if the last cards in the Header have commentary keywords,
1179        this will append the new keyword before the commentary (unless the new
1180        keyword is also commentary).
1181
1182        Also differs from `list.append` in that it can be called with no
1183        arguments: In this case a blank card is appended to the end of the
1184        Header.  In the case all the keyword arguments are ignored.
1185
1186        Parameters
1187        ----------
1188        card : str, tuple
1189            A keyword or a (keyword, value, [comment]) tuple representing a
1190            single header card; the comment is optional in which case a
1191            2-tuple may be used
1192
1193        useblanks : bool, optional
1194            If there are blank cards at the end of the Header, replace the
1195            first blank card so that the total number of cards in the Header
1196            does not increase.  Otherwise preserve the number of blank cards.
1197
1198        bottom : bool, optional
1199            If True, instead of appending after the last non-commentary card,
1200            append after the last non-blank card.
1201
1202        end : bool, optional
1203            If True, ignore the useblanks and bottom options, and append at the
1204            very end of the Header.
1205
1206        """
1207
1208        if isinstance(card, str):
1209            card = Card(card)
1210        elif isinstance(card, tuple):
1211            card = Card(*card)
1212        elif card is None:
1213            card = Card()
1214        elif not isinstance(card, Card):
1215            raise ValueError(
1216                'The value appended to a Header must be either a keyword or '
1217                '(keyword, value, [comment]) tuple; got: {!r}'.format(card))
1218
1219        if not end and card.is_blank:
1220            # Blank cards should always just be appended to the end
1221            end = True
1222
1223        if end:
1224            self._cards.append(card)
1225            idx = len(self._cards) - 1
1226        else:
1227            idx = len(self._cards) - 1
1228            while idx >= 0 and self._cards[idx].is_blank:
1229                idx -= 1
1230
1231            if not bottom and card.keyword not in Card._commentary_keywords:
1232                while (idx >= 0 and
1233                       self._cards[idx].keyword in Card._commentary_keywords):
1234                    idx -= 1
1235
1236            idx += 1
1237            self._cards.insert(idx, card)
1238            self._updateindices(idx)
1239
1240        keyword = Card.normalize_keyword(card.keyword)
1241        self._keyword_indices[keyword].append(idx)
1242        if card.field_specifier is not None:
1243            self._rvkc_indices[card.rawkeyword].append(idx)
1244
1245        if not end:
1246            # If the appended card was a commentary card, and it was appended
1247            # before existing cards with the same keyword, the indices for
1248            # cards with that keyword may have changed
1249            if not bottom and card.keyword in Card._commentary_keywords:
1250                self._keyword_indices[keyword].sort()
1251
1252            # Finally, if useblanks, delete a blank cards from the end
1253            if useblanks and self._countblanks():
1254                # Don't do this unless there is at least one blanks at the end
1255                # of the header; we need to convert the card to its string
1256                # image to see how long it is.  In the vast majority of cases
1257                # this will just be 80 (Card.length) but it may be longer for
1258                # CONTINUE cards
1259                self._useblanks(len(str(card)) // Card.length)
1260
1261        self._modified = True
1262
1263    def extend(self, cards, strip=True, unique=False, update=False,
1264               update_first=False, useblanks=True, bottom=False, end=False):
1265        """
1266        Appends multiple keyword+value cards to the end of the header, similar
1267        to `list.extend`.
1268
1269        Parameters
1270        ----------
1271        cards : iterable
1272            An iterable of (keyword, value, [comment]) tuples; see
1273            `Header.append`.
1274
1275        strip : bool, optional
1276            Remove any keywords that have meaning only to specific types of
1277            HDUs, so that only more general keywords are added from extension
1278            Header or Card list (default: `True`).
1279
1280        unique : bool, optional
1281            If `True`, ensures that no duplicate keywords are appended;
1282            keywords already in this header are simply discarded.  The
1283            exception is commentary keywords (COMMENT, HISTORY, etc.): they are
1284            only treated as duplicates if their values match.
1285
1286        update : bool, optional
1287            If `True`, update the current header with the values and comments
1288            from duplicate keywords in the input header.  This supersedes the
1289            ``unique`` argument.  Commentary keywords are treated the same as
1290            if ``unique=True``.
1291
1292        update_first : bool, optional
1293            If the first keyword in the header is 'SIMPLE', and the first
1294            keyword in the input header is 'XTENSION', the 'SIMPLE' keyword is
1295            replaced by the 'XTENSION' keyword.  Likewise if the first keyword
1296            in the header is 'XTENSION' and the first keyword in the input
1297            header is 'SIMPLE', the 'XTENSION' keyword is replaced by the
1298            'SIMPLE' keyword.  This behavior is otherwise dumb as to whether or
1299            not the resulting header is a valid primary or extension header.
1300            This is mostly provided to support backwards compatibility with the
1301            old ``Header.fromTxtFile`` method, and only applies if
1302            ``update=True``.
1303
1304        useblanks, bottom, end : bool, optional
1305            These arguments are passed to :meth:`Header.append` while appending
1306            new cards to the header.
1307        """
1308
1309        temp = self.__class__(cards)
1310        if strip:
1311            temp.strip()
1312
1313        if len(self):
1314            first = self._cards[0].keyword
1315        else:
1316            first = None
1317
1318        # We don't immediately modify the header, because first we need to sift
1319        # out any duplicates in the new header prior to adding them to the
1320        # existing header, but while *allowing* duplicates from the header
1321        # being extended from (see ticket #156)
1322        extend_cards = []
1323
1324        for idx, card in enumerate(temp.cards):
1325            keyword = card.keyword
1326            if keyword not in Card._commentary_keywords:
1327                if unique and not update and keyword in self:
1328                    continue
1329                elif update:
1330                    if idx == 0 and update_first:
1331                        # Dumbly update the first keyword to either SIMPLE or
1332                        # XTENSION as the case may be, as was in the case in
1333                        # Header.fromTxtFile
1334                        if ((keyword == 'SIMPLE' and first == 'XTENSION') or
1335                                (keyword == 'XTENSION' and first == 'SIMPLE')):
1336                            del self[0]
1337                            self.insert(0, card)
1338                        else:
1339                            self[keyword] = (card.value, card.comment)
1340                    elif keyword in self:
1341                        self[keyword] = (card.value, card.comment)
1342                    else:
1343                        extend_cards.append(card)
1344                else:
1345                    extend_cards.append(card)
1346            else:
1347                if (unique or update) and keyword in self:
1348                    if card.is_blank:
1349                        extend_cards.append(card)
1350                        continue
1351
1352                    for value in self[keyword]:
1353                        if value == card.value:
1354                            break
1355                    else:
1356                        extend_cards.append(card)
1357                else:
1358                    extend_cards.append(card)
1359
1360        for card in extend_cards:
1361            self.append(card, useblanks=useblanks, bottom=bottom, end=end)
1362
1363    def count(self, keyword):
1364        """
1365        Returns the count of the given keyword in the header, similar to
1366        `list.count` if the Header object is treated as a list of keywords.
1367
1368        Parameters
1369        ----------
1370        keyword : str
1371            The keyword to count instances of in the header
1372
1373        """
1374
1375        keyword = Card.normalize_keyword(keyword)
1376
1377        # We have to look before we leap, since otherwise _keyword_indices,
1378        # being a defaultdict, will create an entry for the nonexistent keyword
1379        if keyword not in self._keyword_indices:
1380            raise KeyError(f"Keyword {keyword!r} not found.")
1381
1382        return len(self._keyword_indices[keyword])
1383
1384    def index(self, keyword, start=None, stop=None):
1385        """
1386        Returns the index if the first instance of the given keyword in the
1387        header, similar to `list.index` if the Header object is treated as a
1388        list of keywords.
1389
1390        Parameters
1391        ----------
1392        keyword : str
1393            The keyword to look up in the list of all keywords in the header
1394
1395        start : int, optional
1396            The lower bound for the index
1397
1398        stop : int, optional
1399            The upper bound for the index
1400
1401        """
1402
1403        if start is None:
1404            start = 0
1405
1406        if stop is None:
1407            stop = len(self._cards)
1408
1409        if stop < start:
1410            step = -1
1411        else:
1412            step = 1
1413
1414        norm_keyword = Card.normalize_keyword(keyword)
1415
1416        for idx in range(start, stop, step):
1417            if self._cards[idx].keyword.upper() == norm_keyword:
1418                return idx
1419        else:
1420            raise ValueError(f'The keyword {keyword!r} is not in the  header.')
1421
1422    def insert(self, key, card, useblanks=True, after=False):
1423        """
1424        Inserts a new keyword+value card into the Header at a given location,
1425        similar to `list.insert`.
1426
1427        Parameters
1428        ----------
1429        key : int, str, or tuple
1430            The index into the list of header keywords before which the
1431            new keyword should be inserted, or the name of a keyword before
1432            which the new keyword should be inserted.  Can also accept a
1433            (keyword, index) tuple for inserting around duplicate keywords.
1434
1435        card : str, tuple
1436            A keyword or a (keyword, value, [comment]) tuple; see
1437            `Header.append`
1438
1439        useblanks : bool, optional
1440            If there are blank cards at the end of the Header, replace the
1441            first blank card so that the total number of cards in the Header
1442            does not increase.  Otherwise preserve the number of blank cards.
1443
1444        after : bool, optional
1445            If set to `True`, insert *after* the specified index or keyword,
1446            rather than before it.  Defaults to `False`.
1447        """
1448
1449        if not isinstance(key, numbers.Integral):
1450            # Don't pass through ints to _cardindex because it will not take
1451            # kindly to indices outside the existing number of cards in the
1452            # header, which insert needs to be able to support (for example
1453            # when inserting into empty headers)
1454            idx = self._cardindex(key)
1455        else:
1456            idx = key
1457
1458        if after:
1459            if idx == -1:
1460                idx = len(self._cards)
1461            else:
1462                idx += 1
1463
1464        if idx >= len(self._cards):
1465            # This is just an append (Though it must be an append absolutely to
1466            # the bottom, ignoring blanks, etc.--the point of the insert method
1467            # is that you get exactly what you asked for with no surprises)
1468            self.append(card, end=True)
1469            return
1470
1471        if isinstance(card, str):
1472            card = Card(card)
1473        elif isinstance(card, tuple):
1474            card = Card(*card)
1475        elif not isinstance(card, Card):
1476            raise ValueError(
1477                'The value inserted into a Header must be either a keyword or '
1478                '(keyword, value, [comment]) tuple; got: {!r}'.format(card))
1479
1480        self._cards.insert(idx, card)
1481
1482        keyword = card.keyword
1483
1484        # If idx was < 0, determine the actual index according to the rules
1485        # used by list.insert()
1486        if idx < 0:
1487            idx += len(self._cards) - 1
1488            if idx < 0:
1489                idx = 0
1490
1491        # All the keyword indices above the insertion point must be updated
1492        self._updateindices(idx)
1493
1494        keyword = Card.normalize_keyword(keyword)
1495        self._keyword_indices[keyword].append(idx)
1496        count = len(self._keyword_indices[keyword])
1497        if count > 1:
1498            # There were already keywords with this same name
1499            if keyword not in Card._commentary_keywords:
1500                warnings.warn(
1501                    'A {!r} keyword already exists in this header.  Inserting '
1502                    'duplicate keyword.'.format(keyword), AstropyUserWarning)
1503            self._keyword_indices[keyword].sort()
1504
1505        if card.field_specifier is not None:
1506            # Update the index of RVKC as well
1507            rvkc_indices = self._rvkc_indices[card.rawkeyword]
1508            rvkc_indices.append(idx)
1509            rvkc_indices.sort()
1510
1511        if useblanks:
1512            self._useblanks(len(str(card)) // Card.length)
1513
1514        self._modified = True
1515
1516    def remove(self, keyword, ignore_missing=False, remove_all=False):
1517        """
1518        Removes the first instance of the given keyword from the header similar
1519        to `list.remove` if the Header object is treated as a list of keywords.
1520
1521        Parameters
1522        ----------
1523        keyword : str
1524            The keyword of which to remove the first instance in the header.
1525
1526        ignore_missing : bool, optional
1527            When True, ignores missing keywords.  Otherwise, if the keyword
1528            is not present in the header a KeyError is raised.
1529
1530        remove_all : bool, optional
1531            When True, all instances of keyword will be removed.
1532            Otherwise only the first instance of the given keyword is removed.
1533
1534        """
1535        keyword = Card.normalize_keyword(keyword)
1536        if keyword in self._keyword_indices:
1537            del self[self._keyword_indices[keyword][0]]
1538            if remove_all:
1539                while keyword in self._keyword_indices:
1540                    del self[self._keyword_indices[keyword][0]]
1541        elif not ignore_missing:
1542            raise KeyError(f"Keyword '{keyword}' not found.")
1543
1544    def rename_keyword(self, oldkeyword, newkeyword, force=False):
1545        """
1546        Rename a card's keyword in the header.
1547
1548        Parameters
1549        ----------
1550        oldkeyword : str or int
1551            Old keyword or card index
1552
1553        newkeyword : str
1554            New keyword
1555
1556        force : bool, optional
1557            When `True`, if the new keyword already exists in the header, force
1558            the creation of a duplicate keyword. Otherwise a
1559            `ValueError` is raised.
1560        """
1561
1562        oldkeyword = Card.normalize_keyword(oldkeyword)
1563        newkeyword = Card.normalize_keyword(newkeyword)
1564
1565        if newkeyword == 'CONTINUE':
1566            raise ValueError('Can not rename to CONTINUE')
1567
1568        if (newkeyword in Card._commentary_keywords or
1569                oldkeyword in Card._commentary_keywords):
1570            if not (newkeyword in Card._commentary_keywords and
1571                    oldkeyword in Card._commentary_keywords):
1572                raise ValueError('Regular and commentary keys can not be '
1573                                 'renamed to each other.')
1574        elif not force and newkeyword in self:
1575            raise ValueError(f'Intended keyword {newkeyword} already exists in header.')
1576
1577        idx = self.index(oldkeyword)
1578        card = self._cards[idx]
1579        del self[idx]
1580        self.insert(idx, (newkeyword, card.value, card.comment))
1581
1582    def add_history(self, value, before=None, after=None):
1583        """
1584        Add a ``HISTORY`` card.
1585
1586        Parameters
1587        ----------
1588        value : str
1589            History text to be added.
1590
1591        before : str or int, optional
1592            Same as in `Header.update`
1593
1594        after : str or int, optional
1595            Same as in `Header.update`
1596        """
1597
1598        self._add_commentary('HISTORY', value, before=before, after=after)
1599
1600    def add_comment(self, value, before=None, after=None):
1601        """
1602        Add a ``COMMENT`` card.
1603
1604        Parameters
1605        ----------
1606        value : str
1607            Text to be added.
1608
1609        before : str or int, optional
1610            Same as in `Header.update`
1611
1612        after : str or int, optional
1613            Same as in `Header.update`
1614        """
1615
1616        self._add_commentary('COMMENT', value, before=before, after=after)
1617
1618    def add_blank(self, value='', before=None, after=None):
1619        """
1620        Add a blank card.
1621
1622        Parameters
1623        ----------
1624        value : str, optional
1625            Text to be added.
1626
1627        before : str or int, optional
1628            Same as in `Header.update`
1629
1630        after : str or int, optional
1631            Same as in `Header.update`
1632        """
1633
1634        self._add_commentary('', value, before=before, after=after)
1635
1636    def strip(self):
1637        """
1638        Strip cards specific to a certain kind of header.
1639
1640        Strip cards like ``SIMPLE``, ``BITPIX``, etc. so the rest of
1641        the header can be used to reconstruct another kind of header.
1642        """
1643
1644        # TODO: Previously this only deleted some cards specific to an HDU if
1645        # _hdutype matched that type.  But it seemed simple enough to just
1646        # delete all desired cards anyways, and just ignore the KeyErrors if
1647        # they don't exist.
1648        # However, it might be desirable to make this extendable somehow--have
1649        # a way for HDU classes to specify some headers that are specific only
1650        # to that type, and should be removed otherwise.
1651
1652        naxis = self.get('NAXIS', 0)
1653        tfields = self.get('TFIELDS', 0)
1654
1655        for idx in range(naxis):
1656            self.remove('NAXIS' + str(idx + 1), ignore_missing=True)
1657
1658        for name in ('TFORM', 'TSCAL', 'TZERO', 'TNULL', 'TTYPE',
1659                     'TUNIT', 'TDISP', 'TDIM', 'THEAP', 'TBCOL'):
1660            for idx in range(tfields):
1661                self.remove(name + str(idx + 1), ignore_missing=True)
1662
1663        for name in ('SIMPLE', 'XTENSION', 'BITPIX', 'NAXIS', 'EXTEND',
1664                     'PCOUNT', 'GCOUNT', 'GROUPS', 'BSCALE', 'BZERO',
1665                     'TFIELDS'):
1666            self.remove(name, ignore_missing=True)
1667
1668    def _update(self, card):
1669        """
1670        The real update code.  If keyword already exists, its value and/or
1671        comment will be updated.  Otherwise a new card will be appended.
1672
1673        This will not create a duplicate keyword except in the case of
1674        commentary cards.  The only other way to force creation of a duplicate
1675        is to use the insert(), append(), or extend() methods.
1676        """
1677
1678        keyword, value, comment = card
1679
1680        # Lookups for existing/known keywords are case-insensitive
1681        keyword = keyword.strip().upper()
1682        if keyword.startswith('HIERARCH '):
1683            keyword = keyword[9:]
1684
1685        if (keyword not in Card._commentary_keywords and
1686                keyword in self._keyword_indices):
1687            # Easy; just update the value/comment
1688            idx = self._keyword_indices[keyword][0]
1689            existing_card = self._cards[idx]
1690            existing_card.value = value
1691            if comment is not None:
1692                # '' should be used to explicitly blank a comment
1693                existing_card.comment = comment
1694            if existing_card._modified:
1695                self._modified = True
1696        elif keyword in Card._commentary_keywords:
1697            cards = self._splitcommentary(keyword, value)
1698            if keyword in self._keyword_indices:
1699                # Append after the last keyword of the same type
1700                idx = self.index(keyword, start=len(self) - 1, stop=-1)
1701                isblank = not (keyword or value or comment)
1702                for c in reversed(cards):
1703                    self.insert(idx + 1, c, useblanks=(not isblank))
1704            else:
1705                for c in cards:
1706                    self.append(c, bottom=True)
1707        else:
1708            # A new keyword! self.append() will handle updating _modified
1709            self.append(card)
1710
1711    def _cardindex(self, key):
1712        """Returns an index into the ._cards list given a valid lookup key."""
1713
1714        # This used to just set key = (key, 0) and then go on to act as if the
1715        # user passed in a tuple, but it's much more common to just be given a
1716        # string as the key, so optimize more for that case
1717        if isinstance(key, str):
1718            keyword = key
1719            n = 0
1720        elif isinstance(key, numbers.Integral):
1721            # If < 0, determine the actual index
1722            if key < 0:
1723                key += len(self._cards)
1724            if key < 0 or key >= len(self._cards):
1725                raise IndexError('Header index out of range.')
1726            return key
1727        elif isinstance(key, slice):
1728            return key
1729        elif isinstance(key, tuple):
1730            if (len(key) != 2 or not isinstance(key[0], str) or
1731                    not isinstance(key[1], numbers.Integral)):
1732                raise ValueError(
1733                    'Tuple indices must be 2-tuples consisting of a '
1734                    'keyword string and an integer index.')
1735            keyword, n = key
1736        else:
1737            raise ValueError(
1738                'Header indices must be either a string, a 2-tuple, or '
1739                'an integer.')
1740
1741        keyword = Card.normalize_keyword(keyword)
1742        # Returns the index into _cards for the n-th card with the given
1743        # keyword (where n is 0-based)
1744        indices = self._keyword_indices.get(keyword, None)
1745
1746        if keyword and not indices:
1747            if len(keyword) > KEYWORD_LENGTH or '.' in keyword:
1748                raise KeyError(f"Keyword {keyword!r} not found.")
1749            else:
1750                # Maybe it's a RVKC?
1751                indices = self._rvkc_indices.get(keyword, None)
1752
1753        if not indices:
1754            raise KeyError(f"Keyword {keyword!r} not found.")
1755
1756        try:
1757            return indices[n]
1758        except IndexError:
1759            raise IndexError('There are only {} {!r} cards in the '
1760                             'header.'.format(len(indices), keyword))
1761
1762    def _keyword_from_index(self, idx):
1763        """
1764        Given an integer index, return the (keyword, repeat) tuple that index
1765        refers to.  For most keywords the repeat will always be zero, but it
1766        may be greater than zero for keywords that are duplicated (especially
1767        commentary keywords).
1768
1769        In a sense this is the inverse of self.index, except that it also
1770        supports duplicates.
1771        """
1772
1773        if idx < 0:
1774            idx += len(self._cards)
1775
1776        keyword = self._cards[idx].keyword
1777        keyword = Card.normalize_keyword(keyword)
1778        repeat = self._keyword_indices[keyword].index(idx)
1779        return keyword, repeat
1780
1781    def _relativeinsert(self, card, before=None, after=None, replace=False):
1782        """
1783        Inserts a new card before or after an existing card; used to
1784        implement support for the legacy before/after keyword arguments to
1785        Header.update().
1786
1787        If replace=True, move an existing card with the same keyword.
1788        """
1789
1790        if before is None:
1791            insertionkey = after
1792        else:
1793            insertionkey = before
1794
1795        def get_insertion_idx():
1796            if not (isinstance(insertionkey, numbers.Integral) and
1797                    insertionkey >= len(self._cards)):
1798                idx = self._cardindex(insertionkey)
1799            else:
1800                idx = insertionkey
1801
1802            if before is None:
1803                idx += 1
1804
1805            return idx
1806
1807        if replace:
1808            # The card presumably already exists somewhere in the header.
1809            # Check whether or not we actually have to move it; if it does need
1810            # to be moved we just delete it and then it will be reinserted
1811            # below
1812            old_idx = self._cardindex(card.keyword)
1813            insertion_idx = get_insertion_idx()
1814
1815            if (insertion_idx >= len(self._cards) and
1816                    old_idx == len(self._cards) - 1):
1817                # The card would be appended to the end, but it's already at
1818                # the end
1819                return
1820
1821            if before is not None:
1822                if old_idx == insertion_idx - 1:
1823                    return
1824            elif after is not None and old_idx == insertion_idx:
1825                return
1826
1827            del self[old_idx]
1828
1829        # Even if replace=True, the insertion idx may have changed since the
1830        # old card was deleted
1831        idx = get_insertion_idx()
1832
1833        if card[0] in Card._commentary_keywords:
1834            cards = reversed(self._splitcommentary(card[0], card[1]))
1835        else:
1836            cards = [card]
1837        for c in cards:
1838            self.insert(idx, c)
1839
1840    def _updateindices(self, idx, increment=True):
1841        """
1842        For all cards with index above idx, increment or decrement its index
1843        value in the keyword_indices dict.
1844        """
1845        if idx > len(self._cards):
1846            # Save us some effort
1847            return
1848
1849        increment = 1 if increment else -1
1850
1851        for index_sets in (self._keyword_indices, self._rvkc_indices):
1852            for indices in index_sets.values():
1853                for jdx, keyword_index in enumerate(indices):
1854                    if keyword_index >= idx:
1855                        indices[jdx] += increment
1856
1857    def _countblanks(self):
1858        """Returns the number of blank cards at the end of the Header."""
1859
1860        for idx in range(1, len(self._cards)):
1861            if not self._cards[-idx].is_blank:
1862                return idx - 1
1863        return 0
1864
1865    def _useblanks(self, count):
1866        for _ in range(count):
1867            if self._cards[-1].is_blank:
1868                del self[-1]
1869            else:
1870                break
1871
1872    def _haswildcard(self, keyword):
1873        """Return `True` if the input keyword contains a wildcard pattern."""
1874
1875        return (isinstance(keyword, str) and
1876                (keyword.endswith('...') or '*' in keyword or '?' in keyword))
1877
1878    def _wildcardmatch(self, pattern):
1879        """
1880        Returns a list of indices of the cards matching the given wildcard
1881        pattern.
1882
1883         * '*' matches 0 or more characters
1884         * '?' matches a single character
1885         * '...' matches 0 or more of any non-whitespace character
1886        """
1887
1888        pattern = pattern.replace('*', r'.*').replace('?', r'.')
1889        pattern = pattern.replace('...', r'\S*') + '$'
1890        pattern_re = re.compile(pattern, re.I)
1891
1892        return [idx for idx, card in enumerate(self._cards)
1893                if pattern_re.match(card.keyword)]
1894
1895    def _set_slice(self, key, value, target):
1896        """
1897        Used to implement Header.__setitem__ and CardAccessor.__setitem__.
1898        """
1899
1900        if isinstance(key, slice) or self._haswildcard(key):
1901            if isinstance(key, slice):
1902                indices = range(*key.indices(len(target)))
1903            else:
1904                indices = self._wildcardmatch(key)
1905
1906            if isinstance(value, str) or not isiterable(value):
1907                value = itertools.repeat(value, len(indices))
1908
1909            for idx, val in zip(indices, value):
1910                target[idx] = val
1911
1912            return True
1913
1914        return False
1915
1916    def _splitcommentary(self, keyword, value):
1917        """
1918        Given a commentary keyword and value, returns a list of the one or more
1919        cards needed to represent the full value.  This is primarily used to
1920        create the multiple commentary cards needed to represent a long value
1921        that won't fit into a single commentary card.
1922        """
1923
1924        # The maximum value in each card can be the maximum card length minus
1925        # the maximum key length (which can include spaces if they key length
1926        # less than 8
1927        maxlen = Card.length - KEYWORD_LENGTH
1928        valuestr = str(value)
1929
1930        if len(valuestr) <= maxlen:
1931            # The value can fit in a single card
1932            cards = [Card(keyword, value)]
1933        else:
1934            # The value must be split across multiple consecutive commentary
1935            # cards
1936            idx = 0
1937            cards = []
1938            while idx < len(valuestr):
1939                cards.append(Card(keyword, valuestr[idx:idx + maxlen]))
1940                idx += maxlen
1941        return cards
1942
1943    def _add_commentary(self, key, value, before=None, after=None):
1944        """
1945        Add a commentary card.
1946
1947        If ``before`` and ``after`` are `None`, add to the last occurrence
1948        of cards of the same name (except blank card).  If there is no
1949        card (or blank card), append at the end.
1950        """
1951
1952        if before is not None or after is not None:
1953            self._relativeinsert((key, value), before=before,
1954                                 after=after)
1955        else:
1956            self[key] = value
1957
1958
1959collections.abc.MutableSequence.register(Header)
1960collections.abc.MutableMapping.register(Header)
1961
1962
1963class _DelayedHeader:
1964    """
1965    Descriptor used to create the Header object from the header string that
1966    was stored in HDU._header_str when parsing the file.
1967    """
1968
1969    def __get__(self, obj, owner=None):
1970        try:
1971            return obj.__dict__['_header']
1972        except KeyError:
1973            if obj._header_str is not None:
1974                hdr = Header.fromstring(obj._header_str)
1975                obj._header_str = None
1976            else:
1977                raise AttributeError("'{}' object has no attribute '_header'"
1978                                     .format(obj.__class__.__name__))
1979
1980            obj.__dict__['_header'] = hdr
1981            return hdr
1982
1983    def __set__(self, obj, val):
1984        obj.__dict__['_header'] = val
1985
1986    def __delete__(self, obj):
1987        del obj.__dict__['_header']
1988
1989
1990class _BasicHeaderCards:
1991    """
1992    This class allows to access cards with the _BasicHeader.cards attribute.
1993
1994    This is needed because during the HDU class detection, some HDUs uses
1995    the .cards interface.  Cards cannot be modified here as the _BasicHeader
1996    object will be deleted once the HDU object is created.
1997
1998    """
1999
2000    def __init__(self, header):
2001        self.header = header
2002
2003    def __getitem__(self, key):
2004        # .cards is a list of cards, so key here is an integer.
2005        # get the keyword name from its index.
2006        key = self.header._keys[key]
2007        # then we get the card from the _BasicHeader._cards list, or parse it
2008        # if needed.
2009        try:
2010            return self.header._cards[key]
2011        except KeyError:
2012            cardstr = self.header._raw_cards[key]
2013            card = Card.fromstring(cardstr)
2014            self.header._cards[key] = card
2015            return card
2016
2017
2018class _BasicHeader(collections.abc.Mapping):
2019    """This class provides a fast header parsing, without all the additional
2020    features of the Header class. Here only standard keywords are parsed, no
2021    support for CONTINUE, HIERARCH, COMMENT, HISTORY, or rvkc.
2022
2023    The raw card images are stored and parsed only if needed. The idea is that
2024    to create the HDU objects, only a small subset of standard cards is needed.
2025    Once a card is parsed, which is deferred to the Card class, the Card object
2026    is kept in a cache. This is useful because a small subset of cards is used
2027    a lot in the HDU creation process (NAXIS, XTENSION, ...).
2028
2029    """
2030
2031    def __init__(self, cards):
2032        # dict of (keywords, card images)
2033        self._raw_cards = cards
2034        self._keys = list(cards.keys())
2035        # dict of (keyword, Card object) storing the parsed cards
2036        self._cards = {}
2037        # the _BasicHeaderCards object allows to access Card objects from
2038        # keyword indices
2039        self.cards = _BasicHeaderCards(self)
2040
2041        self._modified = False
2042
2043    def __getitem__(self, key):
2044        if isinstance(key, numbers.Integral):
2045            key = self._keys[key]
2046
2047        try:
2048            return self._cards[key].value
2049        except KeyError:
2050            # parse the Card and store it
2051            cardstr = self._raw_cards[key]
2052            self._cards[key] = card = Card.fromstring(cardstr)
2053            return card.value
2054
2055    def __len__(self):
2056        return len(self._raw_cards)
2057
2058    def __iter__(self):
2059        return iter(self._raw_cards)
2060
2061    def index(self, keyword):
2062        return self._keys.index(keyword)
2063
2064    @classmethod
2065    def fromfile(cls, fileobj):
2066        """The main method to parse a FITS header from a file. The parsing is
2067        done with the parse_header function implemented in Cython."""
2068
2069        close_file = False
2070        if isinstance(fileobj, str):
2071            fileobj = open(fileobj, 'rb')
2072            close_file = True
2073
2074        try:
2075            header_str, cards = parse_header(fileobj)
2076            _check_padding(header_str, BLOCK_SIZE, False)
2077            return header_str, cls(cards)
2078        finally:
2079            if close_file:
2080                fileobj.close()
2081
2082
2083class _CardAccessor:
2084    """
2085    This is a generic class for wrapping a Header in such a way that you can
2086    use the header's slice/filtering capabilities to return a subset of cards
2087    and do something with them.
2088
2089    This is sort of the opposite notion of the old CardList class--whereas
2090    Header used to use CardList to get lists of cards, this uses Header to get
2091    lists of cards.
2092    """
2093
2094    # TODO: Consider giving this dict/list methods like Header itself
2095    def __init__(self, header):
2096        self._header = header
2097
2098    def __repr__(self):
2099        return '\n'.join(repr(c) for c in self._header._cards)
2100
2101    def __len__(self):
2102        return len(self._header._cards)
2103
2104    def __iter__(self):
2105        return iter(self._header._cards)
2106
2107    def __eq__(self, other):
2108        # If the `other` item is a scalar we will still treat it as equal if
2109        # this _CardAccessor only contains one item
2110        if not isiterable(other) or isinstance(other, str):
2111            if len(self) == 1:
2112                other = [other]
2113            else:
2114                return False
2115
2116        for a, b in itertools.zip_longest(self, other):
2117            if a != b:
2118                return False
2119        else:
2120            return True
2121
2122    def __ne__(self, other):
2123        return not (self == other)
2124
2125    def __getitem__(self, item):
2126        if isinstance(item, slice) or self._header._haswildcard(item):
2127            return self.__class__(self._header[item])
2128
2129        idx = self._header._cardindex(item)
2130        return self._header._cards[idx]
2131
2132    def _setslice(self, item, value):
2133        """
2134        Helper for implementing __setitem__ on _CardAccessor subclasses; slices
2135        should always be handled in this same way.
2136        """
2137
2138        if isinstance(item, slice) or self._header._haswildcard(item):
2139            if isinstance(item, slice):
2140                indices = range(*item.indices(len(self)))
2141            else:
2142                indices = self._header._wildcardmatch(item)
2143            if isinstance(value, str) or not isiterable(value):
2144                value = itertools.repeat(value, len(indices))
2145            for idx, val in zip(indices, value):
2146                self[idx] = val
2147            return True
2148        return False
2149
2150
2151class _HeaderComments(_CardAccessor):
2152    """
2153    A class used internally by the Header class for the Header.comments
2154    attribute access.
2155
2156    This object can be used to display all the keyword comments in the Header,
2157    or look up the comments on specific keywords.  It allows all the same forms
2158    of keyword lookup as the Header class itself, but returns comments instead
2159    of values.
2160    """
2161
2162    def __iter__(self):
2163        for card in self._header._cards:
2164            yield card.comment
2165
2166    def __repr__(self):
2167        """Returns a simple list of all keywords and their comments."""
2168
2169        keyword_length = KEYWORD_LENGTH
2170        for card in self._header._cards:
2171            keyword_length = max(keyword_length, len(card.keyword))
2172        return '\n'.join('{:>{len}}  {}'.format(c.keyword, c.comment,
2173                                                len=keyword_length)
2174                         for c in self._header._cards)
2175
2176    def __getitem__(self, item):
2177        """
2178        Slices and filter strings return a new _HeaderComments containing the
2179        returned cards.  Otherwise the comment of a single card is returned.
2180        """
2181
2182        item = super().__getitem__(item)
2183        if isinstance(item, _HeaderComments):
2184            # The item key was a slice
2185            return item
2186        return item.comment
2187
2188    def __setitem__(self, item, comment):
2189        """
2190        Set/update the comment on specified card or cards.
2191
2192        Slice/filter updates work similarly to how Header.__setitem__ works.
2193        """
2194
2195        if self._header._set_slice(item, comment, self):
2196            return
2197
2198        # In this case, key/index errors should be raised; don't update
2199        # comments of nonexistent cards
2200        idx = self._header._cardindex(item)
2201        value = self._header[idx]
2202        self._header[idx] = (value, comment)
2203
2204
2205class _HeaderCommentaryCards(_CardAccessor):
2206    """
2207    This is used to return a list-like sequence over all the values in the
2208    header for a given commentary keyword, such as HISTORY.
2209    """
2210
2211    def __init__(self, header, keyword=''):
2212        super().__init__(header)
2213        self._keyword = keyword
2214        self._count = self._header.count(self._keyword)
2215        self._indices = slice(self._count).indices(self._count)
2216
2217    # __len__ and __iter__ need to be overridden from the base class due to the
2218    # different approach this class has to take for slicing
2219    def __len__(self):
2220        return len(range(*self._indices))
2221
2222    def __iter__(self):
2223        for idx in range(*self._indices):
2224            yield self._header[(self._keyword, idx)]
2225
2226    def __repr__(self):
2227        return '\n'.join(str(x) for x in self)
2228
2229    def __getitem__(self, idx):
2230        if isinstance(idx, slice):
2231            n = self.__class__(self._header, self._keyword)
2232            n._indices = idx.indices(self._count)
2233            return n
2234        elif not isinstance(idx, numbers.Integral):
2235            raise ValueError(f'{self._keyword} index must be an integer')
2236
2237        idx = list(range(*self._indices))[idx]
2238        return self._header[(self._keyword, idx)]
2239
2240    def __setitem__(self, item, value):
2241        """
2242        Set the value of a specified commentary card or cards.
2243
2244        Slice/filter updates work similarly to how Header.__setitem__ works.
2245        """
2246
2247        if self._header._set_slice(item, value, self):
2248            return
2249
2250        # In this case, key/index errors should be raised; don't update
2251        # comments of nonexistent cards
2252        self._header[(self._keyword, item)] = value
2253
2254
2255def _block_size(sep):
2256    """
2257    Determine the size of a FITS header block if a non-blank separator is used
2258    between cards.
2259    """
2260
2261    return BLOCK_SIZE + (len(sep) * (BLOCK_SIZE // Card.length - 1))
2262
2263
2264def _pad_length(stringlen):
2265    """Bytes needed to pad the input stringlen to the next FITS block."""
2266
2267    return (BLOCK_SIZE - (stringlen % BLOCK_SIZE)) % BLOCK_SIZE
2268
2269
2270def _check_padding(header_str, block_size, is_eof, check_block_size=True):
2271    # Strip any zero-padding (see ticket #106)
2272    if header_str and header_str[-1] == '\0':
2273        if is_eof and header_str.strip('\0') == '':
2274            # TODO: Pass this warning to validation framework
2275            warnings.warn(
2276                'Unexpected extra padding at the end of the file.  This '
2277                'padding may not be preserved when saving changes.',
2278                AstropyUserWarning)
2279            raise EOFError()
2280        else:
2281            # Replace the illegal null bytes with spaces as required by
2282            # the FITS standard, and issue a nasty warning
2283            # TODO: Pass this warning to validation framework
2284            warnings.warn(
2285                'Header block contains null bytes instead of spaces for '
2286                'padding, and is not FITS-compliant. Nulls may be '
2287                'replaced with spaces upon writing.', AstropyUserWarning)
2288            header_str.replace('\0', ' ')
2289
2290    if check_block_size and (len(header_str) % block_size) != 0:
2291        # This error message ignores the length of the separator for
2292        # now, but maybe it shouldn't?
2293        actual_len = len(header_str) - block_size + BLOCK_SIZE
2294        # TODO: Pass this error to validation framework
2295        raise ValueError(f'Header size is not multiple of {BLOCK_SIZE}: {actual_len}')
2296