        FUNCTION FXPAR, HDR, NAME, ABORT, COUNT=MATCHES, COMMENT=COMMENTS, $
                        START=START, PRECHECK=PRECHECK, POSTCHECK=POSTCHECK, $
                        NOCONTINUE = NOCONTINUE, DATATYPE=DATATYPE, $
                        NULL=K_NULL, NAN=NAN, MISSING=MISSING, $
                        MULTIVALUE=MULTIVALUE
;+
; NAME: 
;        FXPAR()
; PURPOSE: 
;       Obtain the value of a parameter in a FITS header.
; EXPLANATION: 
;       The first 8 chacters of each element of HDR are searched for a match to
;       NAME.  If the keyword is one of those allowed to take multiple values
;       ("HISTORY", "COMMENT", or "        " (blank)), then the value is taken
;       as the next 72 characters.  Otherwise, it is assumed that the next
;       character is "=", and the value (and optional comment) is then parsed
;       from the last 71 characters.  An error occurs if there is no parameter
;       with the given name.
;      
;       If the value is too long for one line, it may be continued on to the
;       the next input card, using the CONTINUE Long String Keyword convention.
;       For more info, http://fits.gsfc.nasa.gov/registry/continue_keyword.html
;       
;
;       Complex numbers are recognized as two numbers separated by one or more
;       space characters.
;
;       If a numeric value has no decimal point (or E or D) it is returned as
;       type LONG.  If it contains more than 8 numerals, or contains the
;       character 'D', then it is returned as type DOUBLE.  Otherwise it is
;       returned as type FLOAT.    If an integer is too large to be stored as
;       type LONG, then it is returned as DOUBLE.
;
;       If a keyword is in the header and has no value, then the default
;       missing value is returned as explained below.  This can be
;       distinguished from the case where the keyword is not found by the fact
;       that COUNT=0 in that case, while existing keywords without a value will
;       be returned with COUNT=1 or more.
;
; CALLING SEQUENCE: 
;       Result = FXPAR( HDR, NAME  [, ABORT, COUNT=, COMMENT=, /NOCONTINUE ] )
;
;       Result = FXPAR(HEADER,'DATE')           ;Finds the value of DATE
;       Result = FXPAR(HEADER,'NAXIS*')         ;Returns array dimensions as
;                                               ;vector
; REQUIRED INPUTS: 
;       HDR     = FITS header string array (e.g. as returned by FXREAD).  Each
;                 element should have a length of 80 characters
;       NAME    = String name of the parameter to return.  If NAME is of the
;                 form 'keyword*' then an array is returned containing values
;                 of keywordN where N is an integer.  The value of keywordN
;                 will be placed in RESULT(N-1).  The data type of RESULT will
;                 be the type of the first valid match of keywordN
;                 found, unless DATATYPE is given.
; OPTIONAL INPUT: 
;       ABORT   = String specifying that FXPAR should do a RETALL if a
;                 parameter is not found.  ABORT should contain a string to be
;                 printed if the keyword parameter is not found.  If not
;                 supplied, FXPAR will return with a negative !err if a keyword
;                 is not found.
; OUTPUT: 
;       The returned value of the function is the value(s) associated with the
;       requested keyword in the header array.
;
;       If the parameter is complex, double precision, floating point, long or
;       string, then the result is of that type.  Apostrophes are stripped from
;       strings.  If the parameter is logical, 1 is returned for T, and 0 is
;       returned for F.
;
;       If NAME was of form 'keyword*' then a vector of values are returned.
;
; OPTIONAL INPUT KEYWORDS: 
;       DATATYPE = A scalar value, indicating the type of vector
;                  data.  All keywords will be cast to this type.
;                  Default: based on first keyword.
;                  Example: DATATYPE=0.0D (cast data to double precision)
;       START   = A best-guess starting position of the sought-after
;                 keyword in the header.  If specified, then FXPAR
;                 first searches for scalar keywords in the header in
;                 the index range bounded by START-PRECHECK and
;                 START+POSTCHECK.  This can speed up keyword searches
;                 in large headers.  If the keyword is not found, then
;                 FXPAR searches the entire header.  
;
;                 If not specified then the entire header is searched.
;                 Searches of the form 'keyword*' also search the
;                 entire header and ignore START.
;
;                 Upon return START is changed to be the position of
;                 the newly found keyword.  Thus the best way to
;                 search for a series of keywords is to search for
;                 them in the order they appear in the header like
;                 this:
;
;                       START = 0L
;                       P1 = FXPAR('P1', START=START)
;                       P2 = FXPAR('P2', START=START)
;
;       PRECHECK = If START is specified, then PRECHECK is the number
;                  of keywords preceding START to be searched.
;                  Default: 5
;       POSTCHECK = If START is specified, then POSTCHECK is the number
;                   of keywords after START to be searched.
;                   Default: 20
;       /NOCONTINUE = If set, then continuation lines will not be read, even
;                 if present in the header
;       MISSING = By default, this routine returns 0 when keyword values are
;                 not found.  This can be overridden by using the MISSING
;                 keyword, e.g. MISSING=-1.
;       /NAN    = If set, then return Not-a-Number (!values.f_nan) for missing
;                 values.  Ignored if keyword MISSING is present.
;       /NULL   = If set, then return !NULL (undefined) for missing values.
;                 Ignored if MISSING or /NAN is present, or if earlier than IDL
;                 version 8.0.  If multiple values would be returned, then
;                 MISSING= or /NAN should be used instead of /NULL, making sure
;                 that the datatype is consistent with the non-missing values,
;                 e.g. MISSING='' for strings, MISSING=-1 for integers, or
;                 MISSING=-1.0 or /NAN for floating point.  /NAN should not be
;                 used if the datatype would otherwise be integer.
;       /MULTIVALUE = Allow multiple values to be returned, if found in the
;                     header.
; OPTIONAL OUTPUT KEYWORD:
;       COUNT   = Optional keyword to return a value equal to the number of
;                 parameters found by FXPAR.
;       COMMENTS= Array of comments associated with the returned values.
;
; PROCEDURE CALLS: 
;       GETTOK(), VALID_NUM
; SIDE EFFECTS: 
;
;       The system variable !err is set to -1 if parameter not found, 0 for a
;       scalar value returned.  If a vector is returned it is set to the number
;       of keyword matches found.    This use of !ERR is deprecated.
;
;       If a keyword occurs more than once in a header, a warning is given,
;       and the first occurence is used.  However, if the keyword is "HISTORY",
;       "COMMENT", or "        " (blank), then multiple values are returned.
;
; NOTES:
;	The functions SXPAR() and FXPAR() are nearly identical, although
;	FXPAR() has slightly more sophisticated parsing.   There is no
;	particular reason for having two nearly identical procedures, but
;	both are too widely used to drop either one.
;
; REVISION HISTORY: 
;       Version 1, William Thompson, GSFC, 12 April 1993.
;               Adapted from SXPAR
;       Version 2, William Thompson, GSFC, 14 October 1994
;               Modified to use VALID_NUM instead of STRNUMBER.  Inserted
;               additional call to VALID_NUM to trap cases where character
;               strings did not contain quotation marks.
;       Version 3, William Thompson, GSFC, 22 December 1994
;               Fixed bug with blank keywords, following suggestion by Wayne
;               Landsman.
;       Version 4, Mons Morrison, LMSAL, 9-Jan-98
;               Made non-trailing ' for string tag just be a warning (not
;               a fatal error).  It was needed because "sxaddpar" had an
;               error which did not write tags properly for long strings
;               (over 68 characters)
;       Version 5, Wayne Landsman GSFC, 29 May 1998
;               Fixed potential problem with overflow of LONG values
;       Version 6, Craig Markwardt, GSFC, 28 Jan 1998, 
;               Added CONTINUE parsing         
;       Version 7, Craig Markwardt, GSFC, 18 Nov 1999,
;               Added START, PRE/POSTCHECK keywords for better
;               performance
;       Version 8, Craig Markwardt, GSFC, 08 Oct 2003,
;               Added DATATYPE keyword to cast vector keywords type
;       Version 9, Paul Hick, 22 Oct 2003, Corrected bug (NHEADER-1)
;       Version 10, W. Landsman, GSFC  2 May 2012
;               Keywords of form "name_0" could confuse vector extractions
;       Version 11 W. Landsman, GSFC 24 Apr 2014
;               Don't convert LONG64 numbers to to double precision
;       Version 12, William Thompson, 13-Aug-2014
;               Add keywords MISSING, /NAN, and /NULL
;	Version 13, W. Landsman 25-Jan-2018
;		Return ULONG64 integer if LONG64 would overflow
;       Version 14, William Thompson, 03-Jun-2019
;               Add /MULTIVALUE keyword
;       Version 15, Mats Löfdahl, 11-Sep-2019
;               Read CONTINUE mechanism multi-line comments.
;-
;------------------------------------------------------------------------------
;
;  Check the number of parameters.
;
        IF N_PARAMS() LT 2 THEN BEGIN
            PRINT,'Syntax:  result =  FXPAR( HDR, NAME  [, ABORT ])'
            RETURN, -1
        ENDIF
;
;  Determine the default value for missing data.
;
        CASE 1 OF 
            N_ELEMENTS(MISSING) EQ 1: MISSING_VALUE = MISSING
            KEYWORD_SET(NAN): MISSING_VALUE = !VALUES.F_NAN
            KEYWORD_SET(K_NULL) AND !VERSION.RELEASE GE '8.': $
              DUMMY = EXECUTE('MISSING_VALUE = !NULL')
            ELSE: MISSING_VALUE = 0
        ENDCASE
        VALUE = MISSING_VALUE
;
;  Determine the abort condition.
;
        IF N_PARAMS() LE 2 THEN BEGIN
            ABORT_RETURN = 0
            ABORT = 'FITS Header'
        END ELSE ABORT_RETURN = 1
        IF ABORT_RETURN THEN ON_ERROR,1 ELSE ON_ERROR,2
;
;  Check for valid header.  Check header for proper attributes.
;
        S = SIZE(HDR)
        IF ( S[0] NE 1 ) OR ( S[2] NE 7 ) THEN $
            MESSAGE,'FITS Header (first parameter) must be a string array'
;
;  Convert the selected keyword NAME to uppercase.
;
        NAM = STRTRIM( STRUPCASE(NAME) )
;
;  Determine if NAME is of form 'keyword*'.  If so, then strip off the '*', and
;  set the VECTOR flag.  One must consider the possibility that NAM is an empty
;  string.
;
        NAMELENGTH1 = (STRLEN(NAM) - 1) > 1
        IF STRPOS( NAM, '*' ) EQ NAMELENGTH1 THEN BEGIN    
            NAM = STRMID( NAM, 0, NAMELENGTH1)  
            VECTOR = 1                          ;Flag for vector output  
            NAME_LENGTH = STRLEN(NAM)           ;Length of name 
            NUM_LENGTH = 8 - NAME_LENGTH        ;Max length of number portion  
            IF NUM_LENGTH LE 0 THEN MESSAGE,    $
                'Keyword length must be 8 characters or less'
;
;  Otherwise, extend NAME with blanks to eight characters.
;
        ENDIF ELSE BEGIN
            WHILE STRLEN(NAM) LT 8 DO NAM = NAM + ' '
            VECTOR = 0
        ENDELSE
;
;  If of the form 'keyword*', then find all instances of 'keyword' followed by
;  a number.  Store the positions of the located keywords in NFOUND, and the
;  value of the number field in NUMBER.
;
        IF N_ELEMENTS(START)     EQ 0 THEN START = -1L
        START = LONG(START[0])
        IF NOT VECTOR AND START GE 0 THEN BEGIN
            IF N_ELEMENTS(PRECHECK)  EQ 0 THEN PRECHECK = 5
            IF N_ELEMENTS(POSTCHECK) EQ 0 THEN POSTCHECK = 20
            NHEADER = N_ELEMENTS(HDR)
            MN = (START - PRECHECK)  > 0
            MX = (START + POSTCHECK) < (NHEADER-1)      ;Corrected bug
            KEYWORD = STRMID(HDR[MN:MX], 0, 8)
        ENDIF ELSE BEGIN
            RESTART:
            START   = -1L
            KEYWORD = STRMID( HDR, 0, 8)
        ENDELSE

        IF VECTOR THEN BEGIN
            NFOUND = WHERE(STRPOS(KEYWORD,NAM) GE 0, MATCHES)
            IF ( MATCHES GT 0 ) THEN BEGIN
                NUMST= STRMID(HDR[NFOUND], NAME_LENGTH, NUM_LENGTH)
                NUMBER = INTARR(MATCHES)-1
                FOR I = 0, MATCHES-1 DO         $
                    IF VALID_NUM( NUMST[I], NUM) THEN NUMBER[I] = NUM
                IGOOD = WHERE(NUMBER GE 0, MATCHES)
                IF MATCHES GT 0 THEN BEGIN
                    NFOUND = NFOUND[IGOOD]
                    NUMBER = NUMBER[IGOOD]
 		    G = WHERE(NUMBER GT 0, MATCHES)
 		    IF MATCHES GT 0 THEN NUMBER = NUMBER[G]     
		ENDIF
            ENDIF
;
;  Otherwise, find all the instances of the requested keyword.  If more than
;  one is found, and NAME is not one of the special cases, then print an error
;  message.
;
        ENDIF ELSE BEGIN
            NFOUND = WHERE(KEYWORD EQ NAM, MATCHES)
            IF MATCHES EQ 0 AND START GE 0 THEN GOTO, RESTART
            IF START GE 0 THEN NFOUND = NFOUND + MN
            IF (MATCHES GT 1) THEN BEGIN
                IF KEYWORD_SET(MULTIVALUE) THEN BEGIN
                    VECTOR = 1
                    NUMBER = INDGEN(MATCHES) + 1
                END ELSE IF (NAM NE 'HISTORY ') AND (NAM NE 'COMMENT ') $
                  AND (NAM NE '') THEN BEGIN
                    MESSAGE, /INFORMATIONAL, 'WARNING- Keyword ' +   $
                             NAM + 'located more than once in ' + ABORT
                ENDIF
            ENDIF
            IF (MATCHES GT 0) THEN START = NFOUND[MATCHES-1]
        ENDELSE
;
;  Extract the parameter field from the specified header lines.  If one of the
;  special cases, then done.
;
        IF MATCHES GT 0 THEN BEGIN
            VALUE = MISSING_VALUE
            LINE = HDR[NFOUND]
            SVALUE = STRTRIM( STRMID(LINE,9,71),2)
            IF (NAM EQ 'HISTORY ') OR (NAM EQ 'COMMENT ') OR    $
                    (NAM EQ '        ') THEN BEGIN
                VALUE = STRTRIM( STRMID(LINE,8,72),2)
                COMMENTS = STRARR(N_ELEMENTS(VALUE))
;
;  Otherwise, test to see if the parameter contains a string, signalled by
;  beginning with a single quote character (') (apostrophe).
;
            END ELSE FOR I = 0,MATCHES-1 DO BEGIN
                IF ( STRMID(SVALUE[I],0,1) EQ "'" ) THEN BEGIN
                    TEST = STRMID( SVALUE[I],1,STRLEN( SVALUE[I] )-1)
                    NEXT_CHAR = 0
                    OFF = 0
                    VALUE = ''
                    COMMENT = ''
;
;  Find the next apostrophe.
;
NEXT_APOST:
                    ENDAP = STRPOS(TEST, "'", NEXT_CHAR)
                    IF ENDAP LT 0 THEN MESSAGE,         $
                        'WARNING: Value of '+NAME+' invalid in '+ABORT+ " (no trailing ')", /info
                    VALUE = VALUE + STRMID( TEST, NEXT_CHAR, ENDAP-NEXT_CHAR )
;
;  Test to see if the next character is also an apostrophe.  If so, then the
;  string isn't completed yet.  Apostrophes in the text string are signalled as
;  two apostrophes in a row.
;
                    IF STRMID( TEST, ENDAP+1, 1) EQ "'" THEN BEGIN    
                        VALUE = VALUE + "'"
                        NEXT_CHAR = ENDAP+2      
                        GOTO, NEXT_APOST
                    ENDIF
;
;  Extract the comment, if any.
;
                    SLASH = STRPOS(TEST, "/", ENDAP)
                    IF SLASH GE 0 THEN COMMENT += STRMID(TEST, SLASH+1, STRLEN(TEST)-SLASH-1)
;
; CM 19 Sep 1997
; This is a string that could be continued on the next line.  Check this
; possibility with the following four criteria: *1) Ends with '&'
; (2) Next line is CONTINUE  (3) LONGSTRN keyword is present (recursive call to
;  FXPAR) 4. /NOCONTINUE is not set

                    IF NOT KEYWORD_SET(NOCONTINUE) THEN BEGIN
                       OFF = OFF + 1
                       VAL = STRTRIM(VALUE,2)

                       IF (STRLEN(VAL) GT 0) AND $
                      (STRMID(VAL, STRLEN(VAL)-1, 1) EQ '&') AND $
                      (STRMID(HDR[NFOUND[I]+OFF],0,8) EQ 'CONTINUE') THEN BEGIN
                       IF (SIZE(FXPAR(HDR, 'LONGSTRN',/NOCONTINUE)))[1] EQ 7 THEN BEGIN                    
                      VALUE = STRMID(VAL, 0, STRLEN(VAL)-1)
                      TEST = HDR[NFOUND[I]+OFF]
                      TEST = STRMID(TEST, 8, STRLEN(TEST)-8)
                      TEST = STRTRIM(TEST, 2)
                      IF STRMID(TEST, 0, 1) NE "'" THEN MESSAGE, $
                        'ERROR: Invalidly CONTINUEd string in '+ABORT
                      NEXT_CHAR = 1
                      GOTO, NEXT_APOST
                    ENDIF
                   ENDIF
    ENDIF

;
;  If not a string, then separate the parameter field from the comment field.
;  If there is no value field, then use the default "missing" value.
;
                ENDIF ELSE BEGIN
                    VALUE = MISSING_VALUE
                    TEST = SVALUE[I]
                    IF TEST EQ '' THEN BEGIN
                        COMMENT = ''
                        GOTO, GOT_VALUE
                    ENDIF
                    SLASH = STRPOS(TEST, "/")
                    IF SLASH GE 0 THEN BEGIN
                        COMMENT = STRMID(TEST, SLASH+1, STRLEN(TEST)-SLASH-1)
                        IF SLASH GT 0 THEN TEST = STRMID(TEST, 0, SLASH) ELSE $
                            GOTO, GOT_VALUE
                    END ELSE COMMENT = ''
;
;  Find the first word in TEST.  Is it a logical value ('T' or 'F')?
;
                    TEST2 = TEST
                    VALUE = GETTOK(TEST2,' ')
                    TEST2 = STRTRIM(TEST2,2)
                    IF ( VALUE EQ 'T' ) THEN BEGIN
                        VALUE = 1
                    END ELSE IF ( VALUE EQ 'F' ) THEN BEGIN
                        VALUE = 0
                    END ELSE BEGIN
;
;  Test to see if a complex number.  It's a complex number if the value and the
;  next word, if any, both are valid numbers.
;
                        IF STRLEN(TEST2) EQ 0 THEN GOTO, NOT_COMPLEX
                        VALUE2 = GETTOK(TEST2,' ')
                        IF VALID_NUM(VALUE,VAL1) AND VALID_NUM(VALUE2,VAL2) $
                                THEN BEGIN
                            VALUE = COMPLEX(VAL1,VAL2)
                            GOTO, GOT_VALUE
                        ENDIF
;
;  Not a complex number.  Decide if it is a floating point, double precision,
;  or integer number.  If an error occurs, then a string value is returned.
;  If the integer is not within the range of a valid long value, then it will 
;  be converted to a double.  
;
NOT_COMPLEX:
                        ON_IOERROR, GOT_VALUE
                        VALUE = TEST
                        IF NOT VALID_NUM(VALUE) THEN GOTO, GOT_VALUE
                        IF (STRPOS(VALUE,'.') GE 0) OR (STRPOS(VALUE,'E') $
                                GE 0) OR (STRPOS(VALUE,'D') GE 0) THEN BEGIN
                            IF ( STRPOS(VALUE,'D') GT 0 ) OR $
                                    ( STRLEN(VALUE) GE 8 ) THEN BEGIN
                            	VALUE = DOUBLE(VALUE)
                                END ELSE VALUE = FLOAT(VALUE)
                        ENDIF ELSE BEGIN
                            LMAX = 2.0D^31 - 1.0D
                            LMIN = -2.0D^31       ;Typo fixed Feb 2010
                            IF STRMID(VALUE,0,1) NE '-' THEN BEGIN
                            	VALUE = ULONG64(VALUE)
                            	IF VALUE LT ULONG64(2)^63-1 THEN VALUE = LONG64(VALUE)
                            ENDIF ELSE VALUE = LONG64(VALUE)
                            if (VALUE GE LMIN) and (VALUE LE LMAX) THEN $
                                VALUE = LONG(VALUE)
                        ENDELSE
                            
;
GOT_VALUE:
                        ON_IOERROR, NULL
                    ENDELSE
                ENDELSE         ; if string
;
;  Add to vector if required.
;
                IF VECTOR THEN BEGIN
                    MAXNUM = MAX(NUMBER)
                    IF ( I EQ 0 ) THEN BEGIN
                        IF N_ELEMENTS(DATATYPE) EQ 0 THEN BEGIN
                            ;; Data type determined from keyword
                            SZ_VALUE = SIZE(VALUE)
                        ENDIF ELSE BEGIN
                            ;; Data type requested by user
                            SZ_VALUE = SIZE(DATATYPE[0])
                        ENDELSE
                        RESULT = MAKE_ARRAY( MAXNUM, TYPE=SZ_VALUE[1])
                        COMMENTS = STRARR(MAXNUM)
                    ENDIF 
                    RESULT[   NUMBER[I]-1 ] =  VALUE
                    COMMENTS[ NUMBER[I]-1 ] =  COMMENT
                ENDIF ELSE BEGIN
                    COMMENTS = COMMENT
                ENDELSE
            ENDFOR
;
;  Set the value of !ERR for the number of matches for vectors, or simply 0
;  otherwise.
;
            IF VECTOR THEN BEGIN
                !ERR = MATCHES
                RETURN, RESULT
            ENDIF ELSE !ERR = 0
;
;  Error point for keyword not found.
;
        ENDIF ELSE BEGIN
            IF ABORT_RETURN THEN MESSAGE,'Keyword '+NAM+' not found in '+ABORT
            !ERR = -1
        ENDELSE
;
        RETURN, VALUE
        END
