1# cython: language_level=3
2from collections import OrderedDict
3
4cdef Py_ssize_t BLOCK_SIZE = 2880  # the FITS block size
5cdef Py_ssize_t CARD_LENGTH = 80
6cdef str VALUE_INDICATOR = '= '  # The standard FITS value indicator
7cdef str END_CARD = 'END' + ' ' * 77
8
9
10def parse_header(fileobj):
11    """Fast (and incomplete) parser for FITS headers.
12
13    This parser only reads the standard 8 character keywords, and ignores the
14    CONTINUE, COMMENT, HISTORY and HIERARCH cards. The goal is to find quickly
15    the structural keywords needed to build the HDU objects.
16
17    The implementation is straightforward: first iterate on the 2880-bytes
18    blocks, then iterate on the 80-bytes cards, find the value separator, and
19    store the parsed (keyword, card image) in a dictionary.
20
21    """
22
23    cards = OrderedDict()
24    cdef list read_blocks = []
25    cdef int found_end = 0
26    cdef bytes block
27    cdef str header_str, block_str, card_image, keyword
28    cdef Py_ssize_t idx, end_idx, sep_idx
29
30    while found_end == 0:
31        # iterate on blocks
32        block = fileobj.read(BLOCK_SIZE)
33        if not block or len(block) < BLOCK_SIZE:
34            # header looks incorrect, raising exception to fall back to
35            # the full Header parsing
36            raise Exception
37
38        block_str = block.decode('ascii')
39        read_blocks.append(block_str)
40        idx = 0
41        while idx < BLOCK_SIZE:
42            # iterate on cards
43            end_idx = idx + CARD_LENGTH
44            card_image = block_str[idx:end_idx]
45            idx = end_idx
46
47            # We are interested only in standard keyword, so we skip
48            # other cards, e.g. CONTINUE, HIERARCH, COMMENT.
49            if card_image[8:10] == VALUE_INDICATOR:
50                # ok, found standard keyword
51                keyword = card_image[:8].strip()
52                cards[keyword.upper()] = card_image
53            else:
54                sep_idx = card_image.find(VALUE_INDICATOR, 0, 8)
55                if sep_idx > 0:
56                    keyword = card_image[:sep_idx]
57                    cards[keyword.upper()] = card_image
58                elif card_image == END_CARD:
59                    found_end = 1
60                    break
61
62    # we keep the full header string as it may be needed later to
63    # create a Header object
64    header_str = ''.join(read_blocks)
65    return header_str, cards
66