1 /*============================================================================
2   WCSLIB 7.7 - an implementation of the FITS WCS standard.
3   Copyright (C) 1995-2021, Mark Calabretta
4 
5   This file is part of WCSLIB.
6 
7   WCSLIB is free software: you can redistribute it and/or modify it under the
8   terms of the GNU Lesser General Public License as published by the Free
9   Software Foundation, either version 3 of the License, or (at your option)
10   any later version.
11 
12   WCSLIB is distributed in the hope that it will be useful, but WITHOUT ANY
13   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14   FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
15   more details.
16 
17   You should have received a copy of the GNU Lesser General Public License
18   along with WCSLIB.  If not, see http://www.gnu.org/licenses.
19 
20   Author: Mark Calabretta, Australia Telescope National Facility, CSIRO.
21   http://www.atnf.csiro.au/people/Mark.Calabretta
22   $Id: wcspih.l,v 7.7 2021/07/12 06:36:49 mcalabre Exp $
23 *=============================================================================
24 *
25 * wcspih.l is a Flex description file containing the definition of a lexical
26 * scanner for parsing the WCS keyrecords from a FITS primary image or image
27 * extension header.
28 *
29 * wcspih.l requires Flex v2.5.4 or later.  Refer to wcshdr.h for a description
30 * of the user interface and operating notes.
31 *
32 * Implementation notes
33 * --------------------
34 * Use of the WCSAXESa keyword is not mandatory.  Its default value is "the
35 * larger of NAXIS and the largest index of these keywords [i.e. CRPIXj, PCi_j
36 * or CDi_j, CDELTi, CTYPEi, CRVALi, and CUNITi] found in the FITS header".
37 * Consequently the definition of WCSAXESa effectively invalidates the use of
38 * NAXIS for determining the number of coordinate axes and forces a preliminary
39 * pass through the header to determine the "largest index" in headers where
40 * WCSAXESa was omitted.
41 *
42 * Furthermore, since the use of WCSAXESa is optional, there is no way to
43 * determine the number of coordinate representations (the "a" value) other
44 * than by parsing all of the WCS keywords in the header; even if WCSAXESa was
45 * specified for some representations it cannot be known in advance whether it
46 * was specified for all of those present in the header.
47 *
48 * Hence the definition of WCSAXESa forces the scanner to be implemented in two
49 * passes.  The first pass is used to determine the number of coordinate
50 * representations (up to 27) and the number of coordinate axes in each.
51 * Effectively WCSAXESa is ignored unless it exceeds the "largest index" in
52 * which case the keywords for the extra axes assume their default values.  The
53 * number of PVi_ma and PSi_ma keywords in each representation is also counted
54 * in the first pass.
55 *
56 * On completion of the first pass, memory is allocated for an array of the
57 * required number of wcsprm structs and each of these is initialized
58 * appropriately.  These structs are filled in the second pass.
59 *
60 * The parser does not check for duplicated keywords, it accepts the last
61 * encountered.
62 *
63 *===========================================================================*/
64 
65 /* Options. */
66 %option full
67 %option never-interactive
68 %option noinput
69 %option noyywrap
70 %option outfile="wcspih.c"
71 %option prefix="wcspih"
72 %option reentrant
73 %option extra-type="struct wcspih_extra *"
74 
75 /* Indices for parameterized keywords. */
76 Z1	[0-9]
77 Z2	[0-9]{2}
78 Z3	[0-9]{3}
79 Z4	[0-9]{4}
80 Z5	[0-9]{5}
81 Z6	[0-9]{6}
82 
83 I1	[1-9]
84 I2	[1-9][0-9]
85 I3	[1-9][0-9]{2}
86 I4	[1-9][0-9]{3}
87 
88 /* Alternate coordinate system identifier. */
89 ALT	[ A-Z]
90 
91 /* Keyvalue data types. */
92 INTEGER	[+-]?[0-9]+
93 FLOAT	[+-]?([0-9]+\.?[0-9]*|\.[0-9]+)([eEdD][+-]?[0-9]+)?
94 STRING	'([^']|'')*'
95 RECORD	'[^']*'
96 FIELD	[a-zA-Z_][a-zA-Z_0-9.]*
97 
98 /* Inline comment syntax. */
99 INLINE " "*(\/.*)?
100 
101 /* Exclusive start states. */
102 %x CCia CCi_ja CCCCCia CCi_ma CCCCCCCa CCCCCCCC
103 %x CROTAi PROJPn SIP2 SIP3 DSSAMDXY PLTDECSN
104 %x VALUE INTEGER_VAL FLOAT_VAL FLOAT2_VAL STRING_VAL
105 %x RECORD_VAL RECFIELD RECCOLON RECVALUE RECEND
106 %x COMMENT
107 %x DISCARD ERROR FLUSH
108 
109 %{
110 #include <math.h>
111 #include <setjmp.h>
112 #include <stddef.h>
113 #include <stdio.h>
114 #include <stdlib.h>
115 #include <string.h>
116 
117 #include "wcsmath.h"
118 #include "wcsprintf.h"
119 #include "wcsutil.h"
120 
121 #include "dis.h"
122 #include "wcs.h"
123 #include "wcshdr.h"
124 
125 #define INTEGER 0
126 #define FLOAT   1
127 #define FLOAT2  2
128 #define STRING  3
129 #define RECORD  4
130 
131 #define PRIOR   1
132 #define SEQUENT 2
133 
134 #define SIP     1
135 #define DSS     2
136 #define WAT     3
137 
138 // User data associated with yyscanner.
139 struct wcspih_extra {
140   // Values passed to YY_INPUT.
141   char *hdr;
142   int  nkeyrec;
143 
144   // Used in preempting the call to exit() by yy_fatal_error().
145   jmp_buf abort_jmp_env;
146 };
147 
148 #define YY_DECL int wcspih_scanner(char *header, int nkeyrec, int relax, \
149  int ctrl, int *nreject, int *nwcs, struct wcsprm **wcs, yyscan_t yyscanner)
150 
151 #define YY_INPUT(inbuff, count, bufsize) \
152 	{ \
153 	  if (yyextra->nkeyrec) { \
154 	    strncpy(inbuff, yyextra->hdr, 80); \
155 	    inbuff[80] = '\n'; \
156 	    yyextra->hdr += 80; \
157 	    yyextra->nkeyrec--; \
158 	    count = 81; \
159 	  } else { \
160 	    count = YY_NULL; \
161 	  } \
162 	}
163 
164 // Preempt the call to exit() by yy_fatal_error().
165 #define exit(status) longjmp(yyextra->abort_jmp_env, status);
166 
167 // Internal helper functions.
168 static YY_DECL;
169 static int wcspih_final(int ndp[], int ndq[], int distran, double dsstmp[],
170              char *wat[], int *nwcs, struct wcsprm **wcs);
171 static int wcspih_init1(int naxis, int alts[], int dpq[], int npv[],
172              int nps[], int ndp[], int ndq[], int auxprm, int distran,
173              int *nwcs, struct wcsprm **wcs);
174 static void wcspih_pass1(int naxis, int i, int j, char a, int distype,
175              int alts[], int dpq[], int *npptr);
176 
177 static int wcspih_jdref(double *wptr,   const double *jdref);
178 static int wcspih_jdrefi(double *wptr,  const double *jdrefi);
179 static int wcspih_jdreff(double *wptr,  const double *jdreff);
180 static int wcspih_epoch(double *wptr,   const double *epoch);
181 static int wcspih_vsource(double *wptr, const double *vsource);
182 
183 static int wcspih_timepixr(double timepixr);
184 
185 %}
186 
187 %%
188 	int  p, q;
189 	char *errmsg, errtxt[80], *keyname, strtmp[80], *wat[2], *watstr;
190 	int  alts[27], dpq[27], inttmp, ndp[27], ndq[27], nps[27], npv[27],
191 	     rectype;
192 	double dbltmp, dbl2tmp[2], dsstmp[20];
193 	struct auxprm auxtem;
194 	struct disprm distem;
195 	struct wcsprm wcstem;
196 
197 	int naxis = 0;
198 	for (int ialt = 0; ialt < 27; ialt++) {
199 	  alts[ialt] = 0;
200 	  dpq[ialt]  = 0;
201 	  npv[ialt]  = 0;
202 	  nps[ialt]  = 0;
203 	  ndp[ialt]  = 0;
204 	  ndq[ialt]  = 0;
205 	}
206 
207 	// Our handle on the input stream.
208 	char *keyrec = header;
209 	char *hptr = header;
210 	char *keep = 0x0;
211 
212 	// For keeping tallies of keywords found.
213 	*nreject = 0;
214 	int nvalid = 0;
215 	int nother = 0;
216 
217 	// If strict, then also reject.
218 	if (relax & WCSHDR_strict) relax |= WCSHDR_reject;
219 
220 	// Keyword indices, as used in the WCS papers, e.g. PCi_ja, PVi_ma.
221 	int i = 0;
222 	int j = 0;
223 	int m = 0;
224 	char a = ' ';
225 
226 	// For decoding the keyvalue.
227 	int valtype = -1;
228 	int distype =  0;
229 	void *vptr  = 0x0;
230 
231 	// For keywords that require special handling.
232 	int altlin  = 0;
233 	int *npptr  = 0x0;
234 	int (*chekval)(double) = 0x0;
235 	int (*special)(double *, const double *) = 0x0;
236 	int auxprm  = 0;
237 	int naux    = 0;
238 	int distran = 0;
239 	int sipflag = 0;
240 	int dssflag = 0;
241 	int watflag = 0;
242 	int watn    = 0;
243 
244 	// The data structures produced.
245 	*nwcs = 0;
246 	*wcs  = 0x0;
247 
248 	// Control variables.
249 	int ipass = 1;
250 	int npass = 2;
251 
252 	// User data associated with yyscanner.
253 	yyextra->hdr = header;
254 	yyextra->nkeyrec = nkeyrec;
255 
256 	// Return here via longjmp() invoked by yy_fatal_error().
257 	if (setjmp(yyextra->abort_jmp_env)) {
258 	  return WCSHDRERR_PARSER;
259 	}
260 
261 	BEGIN(INITIAL);
262 
263 
264 ^NAXIS"   = "" "*{INTEGER}{INLINE} {
265 	  keyname = "NAXISn";
266 
267 	  if (ipass == 1) {
268 	    sscanf(yytext, "NAXIS   = %d", &naxis);
269 	    if (naxis < 0) naxis = 0;
270 	    BEGIN(FLUSH);
271 
272 	  } else {
273 	    sscanf(yytext, "NAXIS   = %d", &i);
274 
275 	    if (i < 0) {
276 	      errmsg = "negative value of NAXIS ignored";
277 	      BEGIN(ERROR);
278 	    } else {
279 	      BEGIN(DISCARD);
280 	    }
281 	  }
282 	}
283 
284 ^WCSAXES{ALT}=" "" "*{INTEGER} {
285 	  sscanf(yytext, "WCSAXES%c= %d", &a, &i);
286 
287 	  if (i < 0) {
288 	    errmsg = "negative value of WCSAXESa ignored";
289 	    BEGIN(ERROR);
290 
291 	  } else {
292 	    valtype = INTEGER;
293 	    vptr    = 0x0;
294 
295 	    keyname = "WCSAXESa";
296 	    BEGIN(COMMENT);
297 	  }
298 	}
299 
300 ^CRPIX	{
301 	  valtype = FLOAT;
302 	  vptr    = &(wcstem.crpix);
303 
304 	  keyname = "CRPIXja";
305 	  BEGIN(CCCCCia);
306 	}
307 
308 ^PC	{
309 	  valtype = FLOAT;
310 	  vptr    = &(wcstem.pc);
311 	  altlin = 1;
312 
313 	  keyname = "PCi_ja";
314 	  BEGIN(CCi_ja);
315 	}
316 
317 ^CD	{
318 	  valtype = FLOAT;
319 	  vptr    = &(wcstem.cd);
320 	  altlin = 2;
321 
322 	  keyname = "CDi_ja";
323 	  BEGIN(CCi_ja);
324 	}
325 
326 ^CDELT	{
327 	  valtype = FLOAT;
328 	  vptr    = &(wcstem.cdelt);
329 
330 	  keyname = "CDELTia";
331 	  BEGIN(CCCCCia);
332 	}
333 
334 ^CROTA	{
335 	  valtype = FLOAT;
336 	  vptr    = &(wcstem.crota);
337 	  altlin = 4;
338 
339 	  keyname = "CROTAn";
340 	  BEGIN(CROTAi);
341 	}
342 
343 ^CUNIT	{
344 	  valtype = STRING;
345 	  vptr    = &(wcstem.cunit);
346 
347 	  keyname = "CUNITia";
348 	  BEGIN(CCCCCia);
349 	}
350 
351 ^CTYPE	{
352 	  valtype = STRING;
353 	  vptr    = &(wcstem.ctype);
354 
355 	  keyname = "CTYPEia";
356 	  BEGIN(CCCCCia);
357 	}
358 
359 ^CRVAL	{
360 	  valtype = FLOAT;
361 	  vptr    = &(wcstem.crval);
362 
363 	  keyname = "CRVALia";
364 	  BEGIN(CCCCCia);
365 	}
366 
367 ^LONPOLE {
368 	  valtype = FLOAT;
369 	  vptr    = &(wcstem.lonpole);
370 
371 	  keyname = "LONPOLEa";
372 	  BEGIN(CCCCCCCa);
373 	}
374 
375 ^LATPOLE {
376 	  valtype = FLOAT;
377 	  vptr    = &(wcstem.latpole);
378 
379 	  keyname = "LATPOLEa";
380 	  BEGIN(CCCCCCCa);
381 	}
382 
383 ^RESTFRQ {
384 	  valtype = FLOAT;
385 	  vptr    = &(wcstem.restfrq);
386 
387 	  keyname = "RESTFRQa";
388 	  BEGIN(CCCCCCCa);
389 	}
390 
391 ^RESTFREQ {
392 	  if (relax & WCSHDR_strict) {
393 	    errmsg = "the RESTFREQ keyword is deprecated, use RESTFRQa";
394 	    BEGIN(ERROR);
395 
396 	  } else {
397 	    valtype = FLOAT;
398 	    vptr    = &(wcstem.restfrq);
399 
400 	    unput(' ');
401 
402 	    keyname = "RESTFREQ";
403 	    BEGIN(CCCCCCCa);
404 	  }
405 	}
406 
407 ^RESTWAV {
408 	  valtype = FLOAT;
409 	  vptr    = &(wcstem.restwav);
410 
411 	  keyname = "RESTWAVa";
412 	  BEGIN(CCCCCCCa);
413 	}
414 
415 ^PV	{
416 	  valtype = FLOAT;
417 	  vptr    = &(wcstem.pv);
418 	  npptr   = npv;
419 
420 	  keyname = "PVi_ma";
421 	  BEGIN(CCi_ma);
422 	}
423 
424 ^PROJP	{
425 	  valtype = FLOAT;
426 	  vptr    = &(wcstem.pv);
427 	  npptr   = npv;
428 
429 	  keyname = "PROJPn";
430 	  BEGIN(PROJPn);
431 	}
432 
433 ^PS	{
434 	  valtype = STRING;
435 	  vptr    = &(wcstem.ps);
436 	  npptr   = nps;
437 
438 	  keyname = "PSi_ma";
439 	  BEGIN(CCi_ma);
440 	}
441 
442 ^VELREF{ALT}" " {
443 	  sscanf(yytext, "VELREF%c", &a);
444 
445 	  if (relax & WCSHDR_strict) {
446 	    errmsg = "the VELREF keyword is deprecated, use SPECSYSa";
447 	    BEGIN(ERROR);
448 
449 	  } else if ((a == ' ') || (relax & WCSHDR_VELREFa)) {
450 	    valtype = INTEGER;
451 	    vptr    = &(wcstem.velref);
452 
453 	    unput(a);
454 
455 	    keyname = "VELREF";
456 	    BEGIN(CCCCCCCa);
457 
458 	  } else if (relax & WCSHDR_reject) {
459 	    errmsg = "VELREF keyword may not have an alternate version code";
460 	    BEGIN(ERROR);
461 
462 	  } else {
463 	    BEGIN(DISCARD);
464 	  }
465 	}
466 
467 ^CNAME	{
468 	  valtype = STRING;
469 	  vptr    = &(wcstem.cname);
470 
471 	  keyname = "CNAMEia";
472 	  BEGIN(CCCCCia);
473 	}
474 
475 ^CRDER	{
476 	  valtype = FLOAT;
477 	  vptr    = &(wcstem.crder);
478 
479 	  keyname = "CRDERia";
480 	  BEGIN(CCCCCia);
481 	}
482 
483 ^CSYER	{
484 	  valtype = FLOAT;
485 	  vptr    = &(wcstem.csyer);
486 
487 	  keyname = "CSYERia";
488 	  BEGIN(CCCCCia);
489 	}
490 
491 ^CZPHS	{
492 	  valtype = FLOAT;
493 	  vptr    = &(wcstem.czphs);
494 
495 	  keyname = "CZPHSia";
496 	  BEGIN(CCCCCia);
497 	}
498 
499 ^CPERI	{
500 	  valtype = FLOAT;
501 	  vptr    = &(wcstem.cperi);
502 
503 	  keyname = "CPERIia";
504 	  BEGIN(CCCCCia);
505 	}
506 
507 ^WCSNAME {
508 	  valtype = STRING;
509 	  vptr    = wcstem.wcsname;
510 
511 	  keyname = "WCSNAMEa";
512 	  BEGIN(CCCCCCCa);
513 	}
514 
515 ^TIMESYS" " {
516 	  valtype = STRING;
517 	  vptr    = wcstem.timesys;
518 
519 	  keyname = "TIMESYS";
520 	  BEGIN(CCCCCCCC);
521 	}
522 
523 ^TREFPOS" " {
524 	  valtype = STRING;
525 	  vptr    = wcstem.trefpos;
526 
527 	  keyname = "TREFPOS";
528 	  BEGIN(CCCCCCCC);
529 	}
530 
531 ^TREFDIR" " {
532 	  valtype = STRING;
533 	  vptr    = wcstem.trefdir;
534 
535 	  keyname = "TREFDIR";
536 	  BEGIN(CCCCCCCC);
537 	}
538 
539 ^PLEPHEM" " {
540 	  valtype = STRING;
541 	  vptr    = wcstem.plephem;
542 
543 	  keyname = "PLEPHEM";
544 	  BEGIN(CCCCCCCC);
545 	}
546 
547 ^TIMEUNIT {
548 	  valtype = STRING;
549 	  vptr    = wcstem.timeunit;
550 
551 	  keyname = "TIMEUNIT";
552 	  BEGIN(CCCCCCCC);
553 	}
554 
555 ^DATEREF" " |
556 ^DATE-REF {
557 	  if ((yytext[4] == 'R') || (relax & WCSHDR_DATEREF)) {
558 	    valtype = STRING;
559 	    vptr    = wcstem.dateref;
560 
561 	    keyname = "DATEREF";
562 	    BEGIN(CCCCCCCC);
563 
564 	  } else if (relax & WCSHDR_reject) {
565 	    errmsg = "the DATE-REF keyword is non-standard";
566 	    BEGIN(ERROR);
567 
568 	  } else {
569 	    BEGIN(DISCARD);
570 	  }
571 	}
572 
573 ^MJDREF"  " |
574 ^MJD-REF" " {
575 	  if ((yytext[3] == 'R') || (relax & WCSHDR_DATEREF)) {
576 	    valtype = FLOAT2;
577 	    vptr    = wcstem.mjdref;
578 
579 	    keyname = "MJDREF";
580 	    BEGIN(CCCCCCCC);
581 
582 	  } else if (relax & WCSHDR_reject) {
583 	    errmsg = "the MJD-REF keyword is non-standard";
584 	    BEGIN(ERROR);
585 
586 	  } else {
587 	    BEGIN(DISCARD);
588 	  }
589 	}
590 
591 ^MJDREFI" " |
592 ^MJD-REFI {
593 	  if ((yytext[3] == 'R') || (relax & WCSHDR_DATEREF)) {
594 	    // Actually integer, but treated as float.
595 	    valtype = FLOAT;
596 	    vptr    = wcstem.mjdref;
597 
598 	    keyname = "MJDREFI";
599 	    BEGIN(CCCCCCCC);
600 
601 	  } else if (relax & WCSHDR_reject) {
602 	    errmsg = "the MJD-REFI keyword is non-standard";
603 	    BEGIN(ERROR);
604 
605 	  } else {
606 	    BEGIN(DISCARD);
607 	  }
608 	}
609 
610 ^MJDREFF" " |
611 ^MJD-REFF {
612 	  if ((yytext[3] == 'R') || (relax & WCSHDR_DATEREF)) {
613 	    valtype = FLOAT;
614 	    vptr    = wcstem.mjdref + 1;
615 
616 	    keyname = "MJDREFF";
617 	    BEGIN(CCCCCCCC);
618 
619 	  } else if (relax & WCSHDR_reject) {
620 	    errmsg = "the MJD-REFF keyword is non-standard";
621 	    BEGIN(ERROR);
622 
623 	  } else {
624 	    BEGIN(DISCARD);
625 	  }
626 	}
627 
628 ^JDREF"   " |
629 ^JD-REF"  " {
630 	  if ((yytext[2] == 'R') || (relax & WCSHDR_DATEREF)) {
631 	    valtype = FLOAT2;
632 	    vptr    = wcstem.mjdref;
633 	    special = wcspih_jdref;
634 
635 	    keyname = "JDREF";
636 	    BEGIN(CCCCCCCC);
637 
638 	  } else if (relax & WCSHDR_reject) {
639 	    errmsg = "the JD-REF keyword is non-standard";
640 	    BEGIN(ERROR);
641 
642 	  } else {
643 	    BEGIN(DISCARD);
644 	  }
645 	}
646 
647 ^JDREFI"  " |
648 ^JD-REFI {
649 	  if ((yytext[2] == 'R') || (relax & WCSHDR_DATEREF)) {
650 	    // Actually integer, but treated as float.
651 	    valtype = FLOAT;
652 	    vptr    = wcstem.mjdref;
653 	    special = wcspih_jdrefi;
654 
655 	    keyname = "JDREFI";
656 	    BEGIN(CCCCCCCC);
657 
658 	  } else if (relax & WCSHDR_reject) {
659 	    errmsg = "the JD-REFI keyword is non-standard";
660 	    BEGIN(ERROR);
661 
662 	  } else {
663 	    BEGIN(DISCARD);
664 	  }
665 	}
666 
667 ^JDREFF"  " |
668 ^JD-REFF {
669 	  if ((yytext[2] == 'R') || (relax & WCSHDR_DATEREF)) {
670 	    valtype = FLOAT;
671 	    vptr    = wcstem.mjdref;
672 	    special = wcspih_jdreff;
673 
674 	    keyname = "JDREFF";
675 	    BEGIN(CCCCCCCC);
676 
677 	  } else if (relax & WCSHDR_reject) {
678 	    errmsg = "the JD-REFF keyword is non-standard";
679 	    BEGIN(ERROR);
680 
681 	  } else {
682 	    BEGIN(DISCARD);
683 	  }
684 	}
685 
686 ^TIMEOFFS {
687 	  valtype = FLOAT;
688 	  vptr    = &(wcstem.timeoffs);
689 
690 	  keyname = "TIMEOFFS";
691 	  BEGIN(CCCCCCCC);
692 	}
693 
694 ^DATE-OBS {
695 	  valtype = STRING;
696 	  vptr    = wcstem.dateobs;
697 	  if (ctrl < -10) keep = keyrec;
698 
699 	  keyname = "DATE-OBS";
700 	  BEGIN(CCCCCCCC);
701 	}
702 
703 ^DATE-BEG {
704 	  valtype = STRING;
705 	  vptr    = wcstem.datebeg;
706 	  if (ctrl < -10) keep = keyrec;
707 
708 	  keyname = "DATE-BEG";
709 	  BEGIN(CCCCCCCC);
710 	}
711 
712 ^DATE-AVG {
713 	  valtype = STRING;
714 	  vptr    = wcstem.dateavg;
715 	  if (ctrl < -10) keep = keyrec;
716 
717 	  keyname = "DATE-AVG";
718 	  BEGIN(CCCCCCCC);
719 	}
720 
721 ^DATE-END {
722 	  valtype = STRING;
723 	  vptr    = wcstem.dateend;
724 	  if (ctrl < -10) keep = keyrec;
725 
726 	  keyname = "DATE-END";
727 	  BEGIN(CCCCCCCC);
728 	}
729 
730 ^MJD-OBS" " {
731 	  valtype = FLOAT;
732 	  vptr    = &(wcstem.mjdobs);
733 	  if (ctrl < -10) keep = keyrec;
734 
735 	  keyname = "MJD-OBS";
736 	  BEGIN(CCCCCCCC);
737 	}
738 
739 ^MJD-BEG" " {
740 	  valtype = FLOAT;
741 	  vptr    = &(wcstem.mjdbeg);
742 	  if (ctrl < -10) keep = keyrec;
743 
744 	  keyname = "MJD-BEG";
745 	  BEGIN(CCCCCCCC);
746 	}
747 
748 ^MJD-AVG" " {
749 	  valtype = FLOAT;
750 	  vptr    = &(wcstem.mjdavg);
751 	  if (ctrl < -10) keep = keyrec;
752 
753 	  keyname = "MJD-AVG";
754 	  BEGIN(CCCCCCCC);
755 	}
756 
757 ^MJD-END" " {
758 	  valtype = FLOAT;
759 	  vptr    = &(wcstem.mjdend);
760 	  if (ctrl < -10) keep = keyrec;
761 
762 	  keyname = "MJD-END";
763 	  BEGIN(CCCCCCCC);
764 	}
765 
766 ^JEPOCH"  " {
767 	  valtype = FLOAT;
768 	  vptr    = &(wcstem.jepoch);
769 	  if (ctrl < -10) keep = keyrec;
770 
771 	  keyname = "JEPOCH";
772 	  BEGIN(CCCCCCCC);
773 	}
774 
775 ^BEPOCH"  " {
776 	  valtype = FLOAT;
777 	  vptr    = &(wcstem.bepoch);
778 	  if (ctrl < -10) keep = keyrec;
779 
780 	  keyname = "BEPOCH";
781 	  BEGIN(CCCCCCCC);
782 	}
783 
784 ^TSTART"  " {
785 	  valtype = FLOAT;
786 	  vptr    = &(wcstem.tstart);
787 	  if (ctrl < -10) keep = keyrec;
788 
789 	  keyname = "TSTART";
790 	  BEGIN(CCCCCCCC);
791 	}
792 
793 ^TSTOP"   " {
794 	  valtype = FLOAT;
795 	  vptr    = &(wcstem.tstop);
796 	  if (ctrl < -10) keep = keyrec;
797 
798 	  keyname = "TSTOP";
799 	  BEGIN(CCCCCCCC);
800 	}
801 
802 ^XPOSURE" " {
803 	  valtype = FLOAT;
804 	  vptr    = &(wcstem.xposure);
805 	  if (ctrl < -10) keep = keyrec;
806 
807 	  keyname = "XPOSURE";
808 	  BEGIN(CCCCCCCC);
809 	}
810 
811 ^TELAPSE" " {
812 	  valtype = FLOAT;
813 	  vptr    = &(wcstem.telapse);
814 	  if (ctrl < -10) keep = keyrec;
815 
816 	  keyname = "TELAPSE";
817 	  BEGIN(CCCCCCCC);
818 	}
819 
820 ^TIMSYER" " {
821 	  valtype = FLOAT;
822 	  vptr    = &(wcstem.timsyer);
823 	  if (ctrl < -10) keep = keyrec;
824 
825 	  keyname = "TIMSYER";
826 	  BEGIN(CCCCCCCC);
827 	}
828 
829 ^TIMRDER" " {
830 	  valtype = FLOAT;
831 	  vptr    = &(wcstem.timrder);
832 	  if (ctrl < -10) keep = keyrec;
833 
834 	  keyname = "TIMRDER";
835 	  BEGIN(CCCCCCCC);
836 	}
837 
838 ^TIMEDEL" " {
839 	  valtype = FLOAT;
840 	  vptr    = &(wcstem.timedel);
841 	  if (ctrl < -10) keep = keyrec;
842 
843 	  keyname = "TIMEDEL";
844 	  BEGIN(CCCCCCCC);
845 	}
846 
847 ^TIMEPIXR {
848 	  valtype = FLOAT;
849 	  vptr    = &(wcstem.timepixr);
850 	  chekval = wcspih_timepixr;
851 	  if (ctrl < -10) keep = keyrec;
852 
853 	  keyname = "TIMEPIXR";
854 	  BEGIN(CCCCCCCC);
855 	}
856 
857 ^OBSGEO-X {
858 	  valtype = FLOAT;
859 	  vptr    = wcstem.obsgeo;
860 	  if (ctrl < -10) keep = keyrec;
861 
862 	  keyname = "OBSGEO-X";
863 	  BEGIN(CCCCCCCC);
864 	}
865 
866 ^OBSGEO-Y {
867 	  valtype = FLOAT;
868 	  vptr    = wcstem.obsgeo + 1;
869 	  if (ctrl < -10) keep = keyrec;
870 
871 	  keyname = "OBSGEO-Y";
872 	  BEGIN(CCCCCCCC);
873 	}
874 
875 ^OBSGEO-Z {
876 	  valtype = FLOAT;
877 	  vptr    = wcstem.obsgeo + 2;
878 	  if (ctrl < -10) keep = keyrec;
879 
880 	  keyname = "OBSGEO-Z";
881 	  BEGIN(CCCCCCCC);
882 	}
883 
884 ^OBSGEO-L {
885 	  valtype = FLOAT;
886 	  vptr    = wcstem.obsgeo + 3;
887 	  if (ctrl < -10) keep = keyrec;
888 
889 	  keyname = "OBSGEO-L";
890 	  BEGIN(CCCCCCCC);
891 	}
892 
893 ^OBSGEO-B {
894 	  valtype = FLOAT;
895 	  vptr    = wcstem.obsgeo + 4;
896 	  if (ctrl < -10) keep = keyrec;
897 
898 	  keyname = "OBSGEO-B";
899 	  BEGIN(CCCCCCCC);
900 	}
901 
902 ^OBSGEO-H {
903 	  valtype = FLOAT;
904 	  vptr    = wcstem.obsgeo + 5;
905 	  if (ctrl < -10) keep = keyrec;
906 
907 	  keyname = "OBSGEO-H";
908 	  BEGIN(CCCCCCCC);
909 	}
910 
911 ^OBSORBIT {
912 	  valtype = STRING;
913 	  vptr    = wcstem.obsorbit;
914 
915 	  keyname = "OBSORBIT";
916 	  BEGIN(CCCCCCCC);
917 	}
918 
919 ^RADESYS {
920 	  valtype = STRING;
921 	  vptr    = wcstem.radesys;
922 
923 	  keyname = "RADESYSa";
924 	  BEGIN(CCCCCCCa);
925 	}
926 
927 ^RADECSYS {
928 	  if (relax & WCSHDR_RADECSYS) {
929 	    valtype = STRING;
930 	    vptr    = wcstem.radesys;
931 
932 	    unput(' ');
933 
934 	    keyname = "RADECSYS";
935 	    BEGIN(CCCCCCCa);
936 
937 	  } else if (relax & WCSHDR_reject) {
938 	    errmsg = "the RADECSYS keyword is deprecated, use RADESYSa";
939 	    BEGIN(ERROR);
940 
941 	  } else {
942 	    BEGIN(DISCARD);
943 	  }
944 	}
945 
946 ^EPOCH{ALT}"  " {
947 	  sscanf(yytext, "EPOCH%c", &a);
948 
949 	  if (relax & WCSHDR_strict) {
950 	    errmsg = "the EPOCH keyword is deprecated, use EQUINOXa";
951 	    BEGIN(ERROR);
952 
953 	  } else if (a == ' ' || relax & WCSHDR_EPOCHa) {
954 	    valtype = FLOAT;
955 	    vptr    = &(wcstem.equinox);
956 	    special = wcspih_epoch;
957 
958 	    unput(a);
959 
960 	    keyname = "EPOCH";
961 	    BEGIN(CCCCCCCa);
962 
963 	  } else if (relax & WCSHDR_reject) {
964 	    errmsg = "EPOCH keyword may not have an alternate version code";
965 	    BEGIN(ERROR);
966 
967 	  } else {
968 	    BEGIN(DISCARD);
969 	  }
970 	}
971 
972 ^EQUINOX {
973 	  valtype = FLOAT;
974 	  vptr    = &(wcstem.equinox);
975 
976 	  keyname = "EQUINOXa";
977 	  BEGIN(CCCCCCCa);
978 	}
979 
980 ^SPECSYS {
981 	  valtype = STRING;
982 	  vptr    = wcstem.specsys;
983 
984 	  keyname = "SPECSYSa";
985 	  BEGIN(CCCCCCCa);
986 	}
987 
988 ^SSYSOBS {
989 	  valtype = STRING;
990 	  vptr    = wcstem.ssysobs;
991 
992 	  keyname = "SSYSOBSa";
993 	  BEGIN(CCCCCCCa);
994 	}
995 
996 ^VELOSYS {
997 	  valtype = FLOAT;
998 	  vptr    = &(wcstem.velosys);
999 
1000 	  keyname = "VELOSYSa";
1001 	  BEGIN(CCCCCCCa);
1002 	}
1003 
1004 ^VSOURCE{ALT} {
1005 	  if (relax & WCSHDR_VSOURCE) {
1006 	    valtype = FLOAT;
1007 	    vptr    = &(wcstem.zsource);
1008 	    special = wcspih_vsource;
1009 
1010 	    yyless(7);
1011 
1012 	    keyname = "VSOURCEa";
1013 	    BEGIN(CCCCCCCa);
1014 
1015 	  } else if (relax & WCSHDR_reject) {
1016 	    errmsg = "the VSOURCEa keyword is deprecated, use ZSOURCEa";
1017 	    BEGIN(ERROR);
1018 
1019 	  } else {
1020 	    BEGIN(DISCARD);
1021 	  }
1022 	}
1023 
1024 ^ZSOURCE {
1025 	  valtype = FLOAT;
1026 	  vptr    = &(wcstem.zsource);
1027 
1028 	  keyname = "ZSOURCEa";
1029 	  BEGIN(CCCCCCCa);
1030 	}
1031 
1032 ^SSYSSRC {
1033 	  valtype = STRING;
1034 	  vptr    = wcstem.ssyssrc;
1035 
1036 	  keyname = "SSYSSRCa";
1037 	  BEGIN(CCCCCCCa);
1038 	}
1039 
1040 ^VELANGL {
1041 	  valtype = FLOAT;
1042 	  vptr    = &(wcstem.velangl);
1043 
1044 	  keyname = "VELANGLa";
1045 	  BEGIN(CCCCCCCa);
1046 	}
1047 
1048 ^RSUN_REF {
1049 	  valtype = FLOAT;
1050 	  auxprm  = 1;
1051 	  vptr    = &(auxtem.rsun_ref);
1052 
1053 	  keyname = "RSUN_REF";
1054 	  BEGIN(CCCCCCCC);
1055 	}
1056 
1057 ^DSUN_OBS {
1058 	  valtype = FLOAT;
1059 	  auxprm  = 1;
1060 	  vptr    = &(auxtem.dsun_obs);
1061 
1062 	  keyname = "DSUN_OBS";
1063 	  BEGIN(CCCCCCCC);
1064 	}
1065 
1066 ^CRLN_OBS {
1067 	  valtype = FLOAT;
1068 	  auxprm  = 1;
1069 	  vptr    = &(auxtem.crln_obs);
1070 
1071 	  keyname = "CRLN_OBS";
1072 	  BEGIN(CCCCCCCC);
1073 	}
1074 
1075 ^HGLN_OBS {
1076 	  valtype = FLOAT;
1077 	  auxprm  = 1;
1078 	  vptr    = &(auxtem.hgln_obs);
1079 
1080 	  keyname = "HGLN_OBS";
1081 	  BEGIN(CCCCCCCC);
1082 	}
1083 
1084 ^CRLT_OBS |
1085 ^HGLT_OBS {
1086 	  valtype = FLOAT;
1087 	  auxprm  = 1;
1088 	  vptr    = &(auxtem.hglt_obs);
1089 
1090 	  keyname = "HGLT_OBS";
1091 	  BEGIN(CCCCCCCC);
1092 	}
1093 
1094 ^CPDIS	{
1095 	  valtype = STRING;
1096 	  distype = PRIOR;
1097 	  vptr    = &(distem.dtype);
1098 
1099 	  keyname = "CPDISja";
1100 	  BEGIN(CCCCCia);
1101 	}
1102 
1103 ^CQDIS	{
1104 	  valtype = STRING;
1105 	  distype = SEQUENT;
1106 	  vptr    = &(distem.dtype);
1107 
1108 	  keyname = "CQDISia";
1109 	  BEGIN(CCCCCia);
1110 	}
1111 
1112 ^DP	{
1113 	  valtype = RECORD;
1114 	  distype = PRIOR;
1115 	  vptr    = &(distem.dp);
1116 	  npptr   = ndp;
1117 
1118 	  keyname = "DPja";
1119 	  BEGIN(CCia);
1120 	}
1121 
1122 ^DQ	{
1123 	  valtype = RECORD;
1124 	  distype = SEQUENT;
1125 	  vptr    = &(distem.dp);
1126 	  npptr   = ndq;
1127 
1128 	  keyname = "DQia";
1129 	  BEGIN(CCia);
1130 	}
1131 
1132 ^CPERR	{
1133 	  valtype = FLOAT;
1134 	  distype = PRIOR;
1135 	  vptr    = &(distem.maxdis);
1136 
1137 	  keyname = "CPERRja";
1138 	  BEGIN(CCCCCia);
1139 	}
1140 
1141 ^CQERR	{
1142 	  valtype = FLOAT;
1143 	  distype = SEQUENT;
1144 	  vptr    = &(distem.maxdis);
1145 
1146 	  keyname = "CQERRia";
1147 	  BEGIN(CCCCCia);
1148 	}
1149 
1150 ^DVERR	{
1151 	  valtype = FLOAT;
1152 	  distype = PRIOR;
1153 	  vptr    = &(distem.totdis);
1154 
1155 	  keyname = "DVERRa";
1156 	  BEGIN(CCCCCCCa);
1157 	}
1158 
1159 ^A_ORDER" " {
1160 	  // SIP: axis 1 polynomial degree (not stored).
1161 	  valtype = INTEGER;
1162 	  distype = PRIOR;
1163 	  vptr    = 0x0;
1164 
1165 	  i = 1;
1166 	  a = ' ';
1167 
1168 	  keyname = "A_ORDER";
1169 	  BEGIN(VALUE);
1170 	}
1171 
1172 ^B_ORDER" " {
1173 	  // SIP: axis 2 polynomial degree (not stored).
1174 	  valtype = INTEGER;
1175 	  distype = PRIOR;
1176 	  vptr    = 0x0;
1177 
1178 	  i = 2;
1179 	  a = ' ';
1180 
1181 	  keyname = "B_ORDER";
1182 	  BEGIN(VALUE);
1183 	}
1184 
1185 ^AP_ORDER {
1186 	  // SIP: axis 1 inverse polynomial degree (not stored).
1187 	  valtype = INTEGER;
1188 	  distype = PRIOR;
1189 	  vptr    = 0x0;
1190 
1191 	  i = 1;
1192 	  a = ' ';
1193 
1194 	  keyname = "AP_ORDER";
1195 	  BEGIN(VALUE);
1196 	}
1197 
1198 ^BP_ORDER {
1199 	  // SIP: axis 2 inverse polynomial degree (not stored).
1200 	  valtype = INTEGER;
1201 	  distype = PRIOR;
1202 	  vptr    = 0x0;
1203 
1204 	  i = 2;
1205 	  a = ' ';
1206 
1207 	  keyname = "BP_ORDER";
1208 	  BEGIN(VALUE);
1209 	}
1210 
1211 ^A_DMAX"  " {
1212 	  // SIP: axis 1 maximum distortion.
1213 	  valtype = FLOAT;
1214 	  distype = PRIOR;
1215 	  vptr    = &(distem.maxdis);
1216 
1217 	  i = 1;
1218 	  a = ' ';
1219 
1220 	  keyname = "A_DMAX";
1221 	  BEGIN(VALUE);
1222 	}
1223 
1224 ^B_DMAX"  " {
1225 	  // SIP: axis 2 maximum distortion.
1226 	  valtype = FLOAT;
1227 	  distype = PRIOR;
1228 	  vptr    = &(distem.maxdis);
1229 
1230 	  i = 2;
1231 	  a = ' ';
1232 
1233 	  keyname = "B_DMAX";
1234 	  BEGIN(VALUE);
1235 	}
1236 
1237 ^A_	{
1238 	  // SIP: axis 1 polynomial coefficient.
1239 	  i = 1;
1240 	  sipflag = 2;
1241 
1242 	  keyname = "A_p_q";
1243 	  BEGIN(SIP2);
1244 	}
1245 
1246 ^B_	{
1247 	  // SIP: axis 2 polynomial coefficient.
1248 	  i = 2;
1249 	  sipflag = 2;
1250 
1251 	  keyname = "B_p_q";
1252 	  BEGIN(SIP2);
1253 	}
1254 
1255 ^AP_	{
1256 	  // SIP: axis 1 inverse polynomial coefficient.
1257 	  i = 1;
1258 	  sipflag = 3;
1259 
1260 	  keyname = "AP_p_q";
1261 	  BEGIN(SIP3);
1262 	}
1263 
1264 ^BP_	{
1265 	  // SIP: axis 2 inverse polynomial coefficient.
1266 	  i = 2;
1267 	  sipflag = 3;
1268 
1269 	  keyname = "BP_p_q";
1270 	  BEGIN(SIP3);
1271 	}
1272 
1273 ^CNPIX1"  " {
1274 	  // DSS: LLH corner pixel coordinate 1.
1275 	  valtype = FLOAT;
1276 	  distype = SEQUENT;
1277 	  vptr    = dsstmp;
1278 	  dssflag = 1;
1279 	  distran = DSS;
1280 
1281 	  keyname = "CNPIX1";
1282 	  BEGIN(VALUE);
1283 	}
1284 
1285 ^CNPIX2"  " {
1286 	  // DSS: LLH corner pixel coordinate 2.
1287 	  valtype = FLOAT;
1288 	  distype = SEQUENT;
1289 	  vptr    = dsstmp+1;
1290 	  dssflag = 1;
1291 	  distran = DSS;
1292 
1293 	  keyname = "CNPIX1";
1294 	  BEGIN(VALUE);
1295 	}
1296 
1297 ^PPO3"    " {
1298 	  // DSS: plate centre x-coordinate in micron.
1299 	  valtype = FLOAT;
1300 	  distype = SEQUENT;
1301 	  vptr    = dsstmp+2;
1302 	  dssflag = 1;
1303 	  distran = DSS;
1304 
1305 	  keyname = "PPO3";
1306 	  BEGIN(VALUE);
1307 	}
1308 
1309 ^PPO6"    " {
1310 	  // DSS: plate centre y-coordinate in micron.
1311 	  valtype = FLOAT;
1312 	  distype = SEQUENT;
1313 	  vptr    = dsstmp+3;
1314 	  dssflag = 1;
1315 	  distran = DSS;
1316 
1317 	  keyname = "PPO6";
1318 	  BEGIN(VALUE);
1319 	}
1320 
1321 ^XPIXELSZ {
1322 	  // DSS: pixel x-dimension in micron.
1323 	  valtype = FLOAT;
1324 	  distype = SEQUENT;
1325 	  vptr    = dsstmp+4;
1326 	  dssflag = 1;
1327 	  distran = DSS;
1328 
1329 	  keyname = "XPIXELSZ";
1330 	  BEGIN(VALUE);
1331 	}
1332 
1333 ^YPIXELSZ {
1334 	  // DSS: pixel y-dimension in micron.
1335 	  valtype = FLOAT;
1336 	  distype = SEQUENT;
1337 	  vptr    = dsstmp+5;
1338 	  dssflag = 1;
1339 	  distran = DSS;
1340 
1341 	  keyname = "YPIXELSZ";
1342 	  BEGIN(VALUE);
1343 	}
1344 
1345 ^PLTRAH"  " {
1346 	  // DSS: plate centre, right ascension - hours.
1347 	  valtype = FLOAT;
1348 	  distype = SEQUENT;
1349 	  vptr    = dsstmp+6;
1350 	  dssflag = 1;
1351 	  distran = DSS;
1352 
1353 	  keyname = "PLTRAH";
1354 	  BEGIN(VALUE);
1355 	}
1356 
1357 ^PLTRAM"  " {
1358 	  // DSS: plate centre, right ascension - minutes.
1359 	  valtype = FLOAT;
1360 	  distype = SEQUENT;
1361 	  vptr    = dsstmp+7;
1362 	  dssflag = 1;
1363 	  distran = DSS;
1364 
1365 	  keyname = "PLTRAM";
1366 	  BEGIN(VALUE);
1367 	}
1368 
1369 ^PLTRAS"  " {
1370 	  // DSS: plate centre, right ascension - seconds.
1371 	  valtype = FLOAT;
1372 	  distype = SEQUENT;
1373 	  vptr    = dsstmp+8;
1374 	  dssflag = 1;
1375 	  distran = DSS;
1376 
1377 	  keyname = "PLTRAS";
1378 	  BEGIN(VALUE);
1379 	}
1380 
1381 ^PLTDECSN {
1382 	  // DSS: plate centre, declination - sign.
1383 	  valtype = STRING;
1384 	  distype = SEQUENT;
1385 	  vptr    = dsstmp+9;
1386 	  dssflag = 1;
1387 	  distran = DSS;
1388 
1389 	  keyname = "PLTDECSN";
1390 	  BEGIN(PLTDECSN);
1391 	}
1392 
1393 ^PLTDECD" " {
1394 	  // DSS: plate centre, declination - degrees.
1395 	  valtype = FLOAT;
1396 	  distype = SEQUENT;
1397 	  vptr    = dsstmp+10;
1398 	  dssflag = 1;
1399 	  distran = DSS;
1400 
1401 	  keyname = "PLTDECD";
1402 	  BEGIN(VALUE);
1403 	}
1404 
1405 ^PLTDECM" " {
1406 	  // DSS: plate centre, declination - arcmin.
1407 	  valtype = FLOAT;
1408 	  distype = SEQUENT;
1409 	  vptr    = dsstmp+11;
1410 	  dssflag = 1;
1411 	  distran = DSS;
1412 
1413 	  keyname = "PLTDECM";
1414 	  BEGIN(VALUE);
1415 	}
1416 
1417 ^PLTDECS" " {
1418 	  // DSS: plate centre, declination - arcsec.
1419 	  valtype = FLOAT;
1420 	  distype = SEQUENT;
1421 	  vptr    = dsstmp+12;
1422 	  dssflag = 1;
1423 	  distran = DSS;
1424 
1425 	  keyname = "PLTDECS";
1426 	  BEGIN(VALUE);
1427 	}
1428 
1429 ^PLATEID" " {
1430 	  // DSS: plate identification (insufficient to trigger DSS).
1431 	  valtype = STRING;
1432 	  distype = SEQUENT;
1433 	  vptr    = dsstmp+13;
1434 	  dssflag = 2;
1435 	  distran = 0;
1436 
1437 	  keyname = "PLATEID";
1438 	  BEGIN(VALUE);
1439 	}
1440 
1441 ^AMDX	{
1442 	  // DSS: axis 1 polynomial coefficient.
1443 	  i = 1;
1444 	  dssflag = 3;
1445 
1446 	  keyname = "AMDXm";
1447 	  BEGIN(DSSAMDXY);
1448 	}
1449 
1450 ^AMDY	{
1451 	  // DSS: axis 2 polynomial coefficient.
1452 	  i = 2;
1453 	  dssflag = 3;
1454 
1455 	  keyname = "AMDYm";
1456 	  BEGIN(DSSAMDXY);
1457 	}
1458 
1459 ^WAT[12]_{Z3} {
1460 	  // TNX or ZPX: string-encoded data array.
1461 	  sscanf(yytext, "WAT%d_%d", &i, &m);
1462 	  if (watn < m) watn = m;
1463 	  watflag = 1;
1464 
1465 	  valtype = STRING;
1466 	  distype = SEQUENT;
1467 	  vptr = wat[i-1] + 68*(m-1);
1468 
1469 	  a = ' ';
1470 	  distran = WAT;
1471 
1472 	  keyname = "WATi_m";
1473 	  BEGIN(VALUE);
1474 	}
1475 
1476 ^END" "{77} {
1477 	  if (yyextra->nkeyrec) {
1478 	    yyextra->nkeyrec = 0;
1479 	    errmsg = "keyrecords following the END keyrecord were ignored";
1480 	    BEGIN(ERROR);
1481 	  } else {
1482 	    BEGIN(DISCARD);
1483 	  }
1484 	}
1485 
1486 ^.	{
1487 	  BEGIN(DISCARD);
1488 	}
1489 
1490 <CCia>{I1}{ALT}"    " |
1491 <CCia>{I2}{ALT}"   "  |
1492 <CCCCCia>{I1}{ALT}" " |
1493 <CCCCCia>{I2}{ALT} {
1494 	  sscanf(yytext, "%d%c", &i, &a);
1495 	  BEGIN(VALUE);
1496 	}
1497 
1498 <CCia>0{I1}{ALT}"   "    |
1499 <CCia>0{Z1}{I1}{ALT}"  " |
1500 <CCia>0{Z2}{I1}{ALT}" "  |
1501 <CCia>0{Z3}{I1}{ALT}     |
1502 <CCia>0{Z4}{I1}          |
1503 <CCCCCia>0{I1}{ALT}      |
1504 <CCCCCia>0{Z1}{I1} {
1505 	  if (relax & WCSHDR_reject) {
1506 	    // Violates the basic FITS standard.
1507 	    errmsg = "indices in parameterized keywords must not have "
1508 	             "leading zeroes";
1509 	    BEGIN(ERROR);
1510 
1511 	  } else {
1512 	    // Pretend we don't recognize it.
1513 	    BEGIN(DISCARD);
1514 	  }
1515 	}
1516 
1517 <CCia>{Z1}{ALT}"    " |
1518 <CCia>{Z2}{ALT}"   "  |
1519 <CCia>{Z3}{ALT}"  "   |
1520 <CCia>{Z4}{ALT}" "    |
1521 <CCia>{Z5}{ALT}       |
1522 <CCia>{Z6}            |
1523 <CCCCCia>{Z1}{ALT}" " |
1524 <CCCCCia>{Z2}{ALT}    |
1525 <CCCCCia>{Z3} {
1526 	  // Anything that has fallen through to this point must contain
1527 	  // an invalid axis number.
1528 	  errmsg = "axis number must exceed 0";
1529 	  BEGIN(ERROR);
1530 	}
1531 
1532 <CCia>. {
1533 	  // Let it go.
1534 	  BEGIN(DISCARD);
1535 	}
1536 
1537 <CCCCCia>. {
1538 	  if (relax & WCSHDR_reject) {
1539 	    // Looks too much like a FITS WCS keyword not to flag it.
1540 	    errmsg = errtxt;
1541 	    sprintf(errmsg, "keyword looks very much like %s but isn't",
1542 	      keyname);
1543 	    BEGIN(ERROR);
1544 
1545 	  } else {
1546 	    // Let it go.
1547 	    BEGIN(DISCARD);
1548 	  }
1549 	}
1550 
1551 <CCi_ja>{I1}_{I1}{ALT}"  " |
1552 <CCi_ja>{I1}_{I2}{ALT}" " |
1553 <CCi_ja>{I2}_{I1}{ALT}" " |
1554 <CCi_ja>{I2}_{I2}{ALT} {
1555 	  sscanf(yytext, "%d_%d%c", &i, &j, &a);
1556 	  BEGIN(VALUE);
1557 	}
1558 
1559 
1560 <CCi_ja>0{I1}_{I1}{ALT}" " |
1561 <CCi_ja>{I1}_0{I1}{ALT}" " |
1562 <CCi_ja>00{I1}_{I1}{ALT} |
1563 <CCi_ja>0{I1}_0{I1}{ALT} |
1564 <CCi_ja>{I1}_00{I1}{ALT} |
1565 <CCi_ja>000{I1}_{I1} |
1566 <CCi_ja>00{I1}_0{I1} |
1567 <CCi_ja>0{I1}_00{I1} |
1568 <CCi_ja>{I1}_000{I1} |
1569 <CCi_ja>0{I1}_{I2}{ALT} |
1570 <CCi_ja>{I1}_0{I2}{ALT} |
1571 <CCi_ja>00{I1}_{I2} |
1572 <CCi_ja>0{I1}_0{I2} |
1573 <CCi_ja>{I1}_00{I2} |
1574 <CCi_ja>0{I2}_{I1}{ALT} |
1575 <CCi_ja>{I2}_0{I1}{ALT} |
1576 <CCi_ja>00{I2}_{I1} |
1577 <CCi_ja>0{I2}_0{I1} |
1578 <CCi_ja>{I2}_00{I1} |
1579 <CCi_ja>0{I2}_{I2} |
1580 <CCi_ja>{I2}_0{I2} {
1581 	  if (((altlin == 1) && (relax & WCSHDR_PC0i_0ja)) ||
1582 	      ((altlin == 2) && (relax & WCSHDR_CD0i_0ja))) {
1583 	    sscanf(yytext, "%d_%d%c", &i, &j, &a);
1584 	    BEGIN(VALUE);
1585 
1586 	  } else if (relax & WCSHDR_reject) {
1587 	    errmsg = "indices in parameterized keywords must not have "
1588 	             "leading zeroes";
1589 	    BEGIN(ERROR);
1590 
1591 	  } else {
1592 	    // Pretend we don't recognize it.
1593 	    BEGIN(DISCARD);
1594 	  }
1595 	}
1596 
1597 <CCi_ja>{Z1}_{Z1}{ALT}"  " |
1598 <CCi_ja>{Z2}_{Z1}{ALT}" " |
1599 <CCi_ja>{Z1}_{Z2}{ALT}" " |
1600 <CCi_ja>{Z3}_{Z1}{ALT} |
1601 <CCi_ja>{Z2}_{Z2}{ALT} |
1602 <CCi_ja>{Z1}_{Z3}{ALT} |
1603 <CCi_ja>{Z4}_{Z1} |
1604 <CCi_ja>{Z3}_{Z2} |
1605 <CCi_ja>{Z2}_{Z3} |
1606 <CCi_ja>{Z1}_{Z4} {
1607 	  // Anything that has fallen through to this point must contain
1608 	  // an invalid axis number.
1609 	  errmsg = "axis number must exceed 0";
1610 	  BEGIN(ERROR);
1611 	}
1612 
1613 <CCi_ja>{Z1}-{Z1}{ALT}"  " |
1614 <CCi_ja>{Z2}-{Z1}{ALT}" " |
1615 <CCi_ja>{Z1}-{Z2}{ALT}" " |
1616 <CCi_ja>{Z3}-{Z1}{ALT} |
1617 <CCi_ja>{Z2}-{Z2}{ALT} |
1618 <CCi_ja>{Z1}-{Z3}{ALT} |
1619 <CCi_ja>{Z4}-{Z1} |
1620 <CCi_ja>{Z3}-{Z2} |
1621 <CCi_ja>{Z2}-{Z3} |
1622 <CCi_ja>{Z1}-{Z4} {
1623 	  errmsg = errtxt;
1624 	  sprintf(errmsg, "%s keyword must use an underscore, not a dash",
1625 	    keyname);
1626 	  BEGIN(ERROR);
1627 	}
1628 
1629 <CCi_ja>{Z2}{I1}{Z2}{I1} {
1630 	  // This covers the defunct forms CD00i00j and PC00i00j.
1631 	  if (((altlin == 1) && (relax & WCSHDR_PC00i00j)) ||
1632 	      ((altlin == 2) && (relax & WCSHDR_CD00i00j))) {
1633 	    sscanf(yytext, "%3d%3d", &i, &j);
1634 	    a = ' ';
1635 	    BEGIN(VALUE);
1636 
1637 	  } else if (relax & WCSHDR_reject) {
1638 	    errmsg = errtxt;
1639 	    sprintf(errmsg,
1640 	      "this form of the %s keyword is deprecated, use %s",
1641 	      keyname, keyname);
1642 	    BEGIN(ERROR);
1643 
1644 	  } else {
1645 	    // Pretend we don't recognize it.
1646 	    BEGIN(DISCARD);
1647 	  }
1648 	}
1649 
1650 <CCi_ja>. {
1651 	  BEGIN(DISCARD);
1652 	}
1653 
1654 <CCCCCCCa>{ALT} |
1655 <CCCCCCCC>. {
1656 	  if (YY_START == CCCCCCCa) {
1657 	    sscanf(yytext, "%c", &a);
1658 	  } else {
1659 	    unput(yytext[0]);
1660 	    a = 0;
1661 	  }
1662 
1663 	  BEGIN(VALUE);
1664 	}
1665 
1666 <CCCCCCCa>. {
1667 	  if (relax & WCSHDR_reject) {
1668 	    // Looks too much like a FITS WCS keyword not to flag it.
1669 	    errmsg = errtxt;
1670 	    sprintf(errmsg, "invalid alternate code, keyword resembles %s "
1671 	      "but isn't", keyname);
1672 	    BEGIN(ERROR);
1673 
1674 	  } else {
1675 	    // Pretend we don't recognize it.
1676 	    BEGIN(DISCARD);
1677 	  }
1678 	}
1679 
1680 <CCi_ma>{I1}_{Z1}{ALT}"  " |
1681 <CCi_ma>{I1}_{I2}{ALT}" " |
1682 <CCi_ma>{I2}_{Z1}{ALT}" " |
1683 <CCi_ma>{I2}_{I2}{ALT} {
1684 	  sscanf(yytext, "%d_%d%c", &i, &m, &a);
1685 	  BEGIN(VALUE);
1686 	}
1687 
1688 <CCi_ma>0{I1}_{Z1}{ALT}" " |
1689 <CCi_ma>{I1}_0{Z1}{ALT}" " |
1690 <CCi_ma>00{I1}_{Z1}{ALT} |
1691 <CCi_ma>0{I1}_0{Z1}{ALT} |
1692 <CCi_ma>{I1}_00{Z1}{ALT} |
1693 <CCi_ma>000{I1}_{Z1} |
1694 <CCi_ma>00{I1}_0{Z1} |
1695 <CCi_ma>0{I1}_00{Z1} |
1696 <CCi_ma>{I1}_000{Z1} |
1697 <CCi_ma>0{I1}_{I2}{ALT} |
1698 <CCi_ma>{I1}_0{I2}{ALT} |
1699 <CCi_ma>00{I1}_{I2} |
1700 <CCi_ma>0{I1}_0{I2} |
1701 <CCi_ma>{I1}_00{I2} |
1702 <CCi_ma>0{I2}_{Z1}{ALT} |
1703 <CCi_ma>{I2}_0{Z1}{ALT} |
1704 <CCi_ma>00{I2}_{Z1} |
1705 <CCi_ma>0{I2}_0{Z1} |
1706 <CCi_ma>{I2}_00{Z1} |
1707 <CCi_ma>0{I2}_{I2} |
1708 <CCi_ma>{I2}_0{I2} {
1709 	  if (((valtype == FLOAT)  && (relax & WCSHDR_PV0i_0ma)) ||
1710 	      ((valtype == STRING) && (relax & WCSHDR_PS0i_0ma))) {
1711 	    sscanf(yytext, "%d_%d%c", &i, &m, &a);
1712 	    BEGIN(VALUE);
1713 
1714 	  } else if (relax & WCSHDR_reject) {
1715 	    errmsg = "indices in parameterized keywords must not have "
1716 	             "leading zeroes";
1717 	    BEGIN(ERROR);
1718 
1719 	  } else {
1720 	    // Pretend we don't recognize it.
1721 	    BEGIN(DISCARD);
1722 	  }
1723 	}
1724 
1725 <CCi_ma>{Z1}_{Z1}{ALT}"  " |
1726 <CCi_ma>{Z2}_{Z1}{ALT}" " |
1727 <CCi_ma>{Z1}_{Z2}{ALT}" " |
1728 <CCi_ma>{Z3}_{Z1}{ALT} |
1729 <CCi_ma>{Z2}_{Z2}{ALT} |
1730 <CCi_ma>{Z1}_{Z3}{ALT} |
1731 <CCi_ma>{Z4}_{Z1} |
1732 <CCi_ma>{Z3}_{Z2} |
1733 <CCi_ma>{Z2}_{Z3} |
1734 <CCi_ma>{Z1}_{Z4} {
1735 	  // Anything that has fallen through to this point must contain
1736 	  // an invalid axis number.
1737 	  errmsg = "axis number must exceed 0";
1738 	  BEGIN(ERROR);
1739 	}
1740 
1741 <CCi_ma>{Z1}-{Z1}{ALT}"  " |
1742 <CCi_ma>{Z2}-{Z1}{ALT}" " |
1743 <CCi_ma>{Z1}-{Z2}{ALT}" " |
1744 <CCi_ma>{Z3}-{Z1}{ALT} |
1745 <CCi_ma>{Z2}-{Z2}{ALT} |
1746 <CCi_ma>{Z1}-{Z3}{ALT} |
1747 <CCi_ma>{Z4}-{Z1} |
1748 <CCi_ma>{Z3}-{Z2} |
1749 <CCi_ma>{Z2}-{Z3} |
1750 <CCi_ma>{Z1}-{Z4} {
1751 	  errmsg = errtxt;
1752 	  sprintf(errmsg, "%s keyword must use an underscore, not a dash",
1753 	    keyname);
1754 	  BEGIN(ERROR);
1755 	}
1756 
1757 <CCi_ma>. {
1758 	  BEGIN(DISCARD);
1759 	}
1760 
1761 <CROTAi>{Z1}{ALT}" " |
1762 <CROTAi>{Z2}{ALT} |
1763 <CROTAi>{Z3} {
1764 	  a = ' ';
1765 	  sscanf(yytext, "%d%c", &i, &a);
1766 
1767 	  if (relax & WCSHDR_strict) {
1768 	    errmsg = "the CROTAn keyword is deprecated, use PCi_ja";
1769 	    BEGIN(ERROR);
1770 
1771 	  } else if ((a == ' ') || (relax & WCSHDR_CROTAia)) {
1772 	    yyless(0);
1773 	    BEGIN(CCCCCia);
1774 
1775 	  } else if (relax & WCSHDR_reject) {
1776 	    errmsg = "CROTAn keyword may not have an alternate version code";
1777 	    BEGIN(ERROR);
1778 
1779 	  } else {
1780 	    // Pretend we don't recognize it.
1781 	    BEGIN(DISCARD);
1782 	  }
1783 	}
1784 
1785 <CROTAi>. {
1786 	  yyless(0);
1787 	  BEGIN(CCCCCia);
1788 	}
1789 
1790 <PROJPn>{Z1}"  " {
1791 	  if (relax & WCSHDR_PROJPn) {
1792 	    sscanf(yytext, "%d", &m);
1793 	    i = 0;
1794 	    a = ' ';
1795 	    BEGIN(VALUE);
1796 
1797 	  } else if (relax & WCSHDR_reject) {
1798 	    errmsg = "the PROJPn keyword is deprecated, use PVi_ma";
1799 	    BEGIN(ERROR);
1800 
1801 	  } else {
1802 	    BEGIN(DISCARD);
1803 	  }
1804 	}
1805 
1806 <PROJPn>{Z2}" " |
1807 <PROJPn>{Z3} {
1808 	  if (relax & (WCSHDR_PROJPn | WCSHDR_reject)) {
1809 	    errmsg = "invalid PROJPn keyword";
1810 	    BEGIN(ERROR);
1811 
1812 	  } else {
1813 	    BEGIN(DISCARD);
1814 	  }
1815 	}
1816 
1817 <PROJPn>. {
1818 	  BEGIN(DISCARD);
1819 	}
1820 
1821 <SIP2>{Z1}_{Z1}"   " |
1822 <SIP3>{Z1}_{Z1}"  " {
1823 	  // SIP keywords.
1824 	  valtype = FLOAT;
1825 	  distype = PRIOR;
1826 	  vptr    = &(distem.dp);
1827 	  npptr   = ndp;
1828 
1829 	  a = ' ';
1830 	  distran = SIP;
1831 
1832 	  sscanf(yytext, "%d_%d", &p, &q);
1833 	  BEGIN(VALUE);
1834 	}
1835 
1836 <SIP2>. |
1837 <SIP3>. {
1838 	  BEGIN(DISCARD);
1839 	}
1840 
1841 <DSSAMDXY>{I1}"   " |
1842 <DSSAMDXY>{I2}"  " {
1843 	  // DSS keywords.
1844 	  valtype = FLOAT;
1845 	  distype = SEQUENT;
1846 	  vptr    = &(distem.dp);
1847 	  npptr   = ndq;
1848 
1849 	  a = ' ';
1850 	  distran = DSS;
1851 
1852 	  sscanf(yytext, "%d", &m);
1853 	  BEGIN(VALUE);
1854 	}
1855 
1856 <DSSAMDXY>. {
1857 	  BEGIN(DISCARD);
1858 	}
1859 
1860 <PLTDECSN>=" "+{STRING} {
1861 	  // Special handling for this iconic DSS keyword.
1862 	  if (1 < ipass) {
1863 	    // Look for a minus sign.
1864 	    sscanf(yytext, "= '%s", strtmp);
1865 	    dbltmp = strcmp(strtmp, "-") ? 1.0 : -1.0;
1866 	  }
1867 
1868 	  BEGIN(COMMENT);
1869 	}
1870 
1871 <PLTDECSN>. {
1872 	  BEGIN(DISCARD);
1873 	}
1874 
1875 <VALUE>=" "+ {
1876 	  // Do checks on i, j & m.
1877 	  if (99 < i || 99 < j || 99 < m) {
1878 	    if (relax & WCSHDR_reject) {
1879 	      if (99 < i || 99 < j) {
1880 	        errmsg = "axis number exceeds 99";
1881 	      } else if (m > 99) {
1882 	        errmsg = "parameter number exceeds 99";
1883 	      }
1884 	      BEGIN(ERROR);
1885 
1886 	    } else {
1887 	      // Pretend we don't recognize it.
1888 	      BEGIN(DISCARD);
1889 	    }
1890 
1891 	  } else {
1892 	    if (valtype == INTEGER) {
1893 	      BEGIN(INTEGER_VAL);
1894 	    } else if (valtype == FLOAT) {
1895 	      BEGIN(FLOAT_VAL);
1896 	    } else if (valtype == FLOAT2) {
1897 	      BEGIN(FLOAT2_VAL);
1898 	    } else if (valtype == STRING) {
1899 	      BEGIN(STRING_VAL);
1900 	    } else if (valtype == RECORD) {
1901 	      BEGIN(RECORD_VAL);
1902 	    } else {
1903 	      errmsg = errtxt;
1904 	      sprintf(errmsg, "internal parser ERROR, bad data type: %d",
1905 	        valtype);
1906 	      BEGIN(ERROR);
1907 	    }
1908 	  }
1909 	}
1910 
1911 <VALUE>. {
1912 	  errmsg = "invalid KEYWORD = VALUE syntax";
1913 	  BEGIN(ERROR);
1914 	}
1915 
1916 <INTEGER_VAL>{INTEGER} {
1917 	  if (ipass == 1) {
1918 	    BEGIN(COMMENT);
1919 
1920 	  } else {
1921 	    // Read the keyvalue.
1922 	    sscanf(yytext, "%d", &inttmp);
1923 
1924 	    BEGIN(COMMENT);
1925 	  }
1926 	}
1927 
1928 <INTEGER_VAL>. {
1929 	  errmsg = "an integer value was expected";
1930 	  BEGIN(ERROR);
1931 	}
1932 
1933 <FLOAT_VAL>{FLOAT} {
1934 	  if (ipass == 1) {
1935 	    BEGIN(COMMENT);
1936 
1937 	  } else {
1938 	    // Read the keyvalue.
1939 	    wcsutil_str2double(yytext, &dbltmp);
1940 
1941 	    if (chekval && chekval(dbltmp)) {
1942 	      errmsg = "invalid keyvalue";
1943 	      BEGIN(ERROR);
1944 	    } else {
1945 	      BEGIN(COMMENT);
1946 	    }
1947 	  }
1948 	}
1949 
1950 <FLOAT_VAL>. {
1951 	  errmsg = "a floating-point value was expected";
1952 	  BEGIN(ERROR);
1953 	}
1954 
1955 <FLOAT2_VAL>{FLOAT} {
1956 	  if (ipass == 1) {
1957 	    BEGIN(COMMENT);
1958 
1959 	  } else {
1960 	    // Read the keyvalue as integer and fractional parts.
1961 	    wcsutil_str2double2(yytext, dbl2tmp);
1962 
1963 	    BEGIN(COMMENT);
1964 	  }
1965 	}
1966 
1967 <FLOAT2_VAL>. {
1968 	  errmsg = "a floating-point value was expected";
1969 	  BEGIN(ERROR);
1970 	}
1971 
1972 <STRING_VAL>{STRING} {
1973 	  if (ipass == 1) {
1974 	    BEGIN(COMMENT);
1975 
1976 	  } else {
1977 	    // Read the keyvalue.
1978 	    strcpy(strtmp, yytext+1);
1979 
1980 	    // Squeeze out repeated quotes.
1981 	    int ix = 0;
1982 	    for (int jx = 0; jx < 72; jx++) {
1983 	      if (ix < jx) {
1984 	        strtmp[ix] = strtmp[jx];
1985 	      }
1986 
1987 	      if (strtmp[jx] == '\0') {
1988 	        if (ix) strtmp[ix-1] = '\0';
1989 	        break;
1990 	      } else if (strtmp[jx] == '\'' && strtmp[jx+1] == '\'') {
1991 	        jx++;
1992 	      }
1993 
1994 	      ix++;
1995 	    }
1996 
1997 	    BEGIN(COMMENT);
1998 	  }
1999 	}
2000 
2001 <STRING_VAL>. {
2002 	  errmsg = "a string value was expected";
2003 	  BEGIN(ERROR);
2004 	}
2005 
2006 <RECORD_VAL>{RECORD} {
2007 	  if (ipass == 1) {
2008 	    BEGIN(COMMENT);
2009 
2010 	  } else {
2011 	    yyless(1);
2012 
2013 	    BEGIN(RECFIELD);
2014 	  }
2015 	}
2016 
2017 <RECORD_VAL>. {
2018 	  errmsg = "a record was expected";
2019 	  BEGIN(ERROR);
2020 	}
2021 
2022 <RECFIELD>{FIELD} {
2023 	  strcpy(strtmp, yytext);
2024 	  BEGIN(RECCOLON);
2025 	}
2026 
2027 <RECFIELD>. {
2028 	  errmsg = "invalid record field";
2029 	  BEGIN(ERROR);
2030 	}
2031 
2032 <RECCOLON>:" "+ {
2033 	  BEGIN(RECVALUE);
2034 	}
2035 
2036 <RECCOLON>. {
2037 	  errmsg = "invalid record syntax";
2038 	  BEGIN(ERROR);
2039 	}
2040 
2041 <RECVALUE>{INTEGER} {
2042 	  rectype = 0;
2043 	  sscanf(yytext, "%d", &inttmp);
2044 	  BEGIN(RECEND);
2045 	}
2046 
2047 <RECVALUE>{FLOAT} {
2048 	  rectype = 1;
2049 	  wcsutil_str2double(yytext, &dbltmp);
2050 	  BEGIN(RECEND);
2051 	}
2052 
2053 <RECVALUE>. {
2054 	  errmsg = "invalid record value";
2055 	  BEGIN(ERROR);
2056 	}
2057 
2058 <RECEND>' {
2059 	  BEGIN(COMMENT);
2060 	}
2061 
2062 <COMMENT>{INLINE}$ {
2063 	  if (ipass == 1) {
2064 	    // Do first-pass bookkeeping.
2065 	    wcspih_pass1(naxis, i, j, a, distype, alts, dpq, npptr);
2066 	    BEGIN(FLUSH);
2067 
2068 	  } else if (*wcs) {
2069 	    // Store the value now that the keyrecord has been validated.
2070 	    int gotone = 0;
2071 	    for (int ialt = 0; ialt < *nwcs; ialt++) {
2072 	      // The loop here is for keywords that apply
2073 	      // to every alternate; these have a == 0.
2074 	      if (a >= 'A') {
2075 	        ialt = alts[a-'A'+1];
2076 	        if (ialt < 0) break;
2077 	      }
2078 	      gotone = 1;
2079 
2080 	      if (vptr) {
2081 	        if (sipflag) {
2082 	          // Translate a SIP keyword into DPja.
2083 	          struct disprm *disp = (*wcs)->lin.dispre;
2084 	          int ipx = (disp->ndp)++;
2085 
2086 	          // SIP doesn't have alternates.
2087 		  char keyword[16];
2088 	          sprintf(keyword, "DP%d", i);
2089 	          sprintf(strtmp, "SIP.%s.%d_%d", (sipflag==2)?"FWD":"REV",
2090 	                  p, q);
2091 	          if (valtype == INTEGER) {
2092 	            dpfill(disp->dp+ipx, keyword, strtmp, i, 0, inttmp, 0.0);
2093 	          } else {
2094 	            dpfill(disp->dp+ipx, keyword, strtmp, i, 1, 0, dbltmp);
2095 	          }
2096 
2097 	        } else if (dssflag) {
2098 	          // All DSS keywords require special handling.
2099 	          if (dssflag == 1) {
2100 	            // Temporary parameter for DSS used by wcspih_final().
2101 	            *((double *)vptr) = dbltmp;
2102 
2103 	          } else if (dssflag == 2) {
2104 	            // Temporary parameter for DSS used by wcspih_final().
2105 	            strcpy((char *)vptr, strtmp);
2106 
2107 	          } else {
2108 	            // Translate a DSS keyword into DQia.
2109 	            if (m <= 13 || dbltmp != 0.0) {
2110 	              struct disprm *disp = (*wcs)->lin.disseq;
2111 	              int ipx = (disp->ndp)++;
2112 
2113 	              // DSS doesn't have alternates.
2114 		      char keyword[16];
2115 	              sprintf(keyword, "DQ%d", i);
2116 	              sprintf(strtmp, "DSS.AMD.%d", m);
2117 	              dpfill(disp->dp+ipx, keyword, strtmp, i, 1, 0, dbltmp);
2118 
2119 	              // Also required by wcspih_final().
2120 	              if (m <= 3) {
2121 	                dsstmp[13+(i-1)*3+m] = dbltmp;
2122 	              }
2123 	            }
2124 	          }
2125 
2126 	        } else if (watflag) {
2127 	          // String array for TNX and ZPX used by wcspih_final().
2128 	          strcpy((char *)vptr, strtmp);
2129 
2130 	        } else {
2131 	          // An "ordinary" keyword.
2132 	          struct wcsprm *wcsp = *wcs + ialt;
2133 		  struct disprm *disp;
2134 	          void *wptr;
2135 	          ptrdiff_t voff;
2136 	          if (auxprm) {
2137 	            // Additional auxiliary parameter.
2138 	            struct auxprm *auxp = wcsp->aux;
2139 	            voff = (char *)vptr - (char *)(&auxtem);
2140 	            wptr = (void *)((char *)auxp + voff);
2141 
2142 	          } else if (distype) {
2143 	            // Distortion parameter of some kind.
2144 	            if (distype == PRIOR) {
2145 	              // Prior distortion.
2146 	              disp = wcsp->lin.dispre;
2147 	            } else {
2148 	              // Sequent distortion.
2149 	              disp = wcsp->lin.disseq;
2150 	            }
2151 	            voff = (char *)vptr - (char *)(&distem);
2152 	            wptr = (void *)((char *)disp + voff);
2153 
2154 	          } else {
2155 	            // A parameter that lives directly in wcsprm.
2156 	            voff = (char *)vptr - (char *)(&wcstem);
2157 	            wptr = (void *)((char *)wcsp + voff);
2158 	          }
2159 
2160 	          if (valtype == INTEGER) {
2161 	            *((int *)wptr) = inttmp;
2162 
2163 	          } else if (valtype == FLOAT) {
2164 	            // Apply keyword parameterization.
2165 	            if (npptr == npv) {
2166 	              int ipx = (wcsp->npv)++;
2167 	              wcsp->pv[ipx].i = i;
2168 	              wcsp->pv[ipx].m = m;
2169 	              wptr = &(wcsp->pv[ipx].value);
2170 
2171 	            } else if (j) {
2172 	              wptr = *((double **)wptr) + (i - 1)*(wcsp->naxis)
2173 	                                        + (j - 1);
2174 
2175 	            } else if (i) {
2176 	              wptr = *((double **)wptr) + (i - 1);
2177 	            }
2178 
2179 	            if (special) {
2180 	              special(wptr, &dbltmp);
2181 	            } else {
2182 	              *((double *)wptr) = dbltmp;
2183 	            }
2184 
2185 	            // Flag presence of PCi_ja, or CDi_ja and/or CROTAia.
2186 	            if (altlin) {
2187 	              wcsp->altlin |= altlin;
2188 	              altlin = 0;
2189 	            }
2190 
2191 	          } else if (valtype == FLOAT2) {
2192 	            // Split MJDREF and JDREF into integer and fraction.
2193 	            if (special) {
2194 	              special(wptr, dbl2tmp);
2195 	            } else {
2196 	              *((double *)wptr) = dbl2tmp[0];
2197 	              *((double *)wptr + 1) = dbl2tmp[1];
2198 	            }
2199 
2200 	          } else if (valtype == STRING) {
2201 	            // Apply keyword parameterization.
2202 	            if (npptr == nps) {
2203 	              int ipx = (wcsp->nps)++;
2204 	              wcsp->ps[ipx].i = i;
2205 	              wcsp->ps[ipx].m = m;
2206 	              wptr = wcsp->ps[ipx].value;
2207 
2208 	            } else if (j) {
2209 	              wptr = *((char (**)[72])wptr) +
2210 	                      (i - 1)*(wcsp->naxis) + (j - 1);
2211 
2212 	            } else if (i) {
2213 	              wptr = *((char (**)[72])wptr) + (i - 1);
2214 	            }
2215 
2216 	            char *cptr = (char *)wptr;
2217 	            strcpy(cptr, strtmp);
2218 
2219 	          } else if (valtype == RECORD) {
2220 	            int ipx = (disp->ndp)++;
2221 
2222 		    char keyword[16];
2223 	            if (a == ' ') {
2224 	              sprintf(keyword, "%.2s%d", keyname, i);
2225 	            } else {
2226 	              sprintf(keyword, "%.2s%d%c", keyname, i, a);
2227 	            }
2228 
2229 	            dpfill(disp->dp+ipx, keyword, strtmp, i, rectype, inttmp,
2230 	                   dbltmp);
2231 	          }
2232 	        }
2233 	      }
2234 
2235 	      if (a) break;
2236 	    }
2237 
2238 	    if (gotone) {
2239 	      nvalid++;
2240 	      if (ctrl == 4) {
2241 	        if (distran || dssflag) {
2242 	          wcsfprintf(stderr, "%.80s\n  Accepted (%d) as a "
2243 	            "recognized WCS convention.\n", keyrec, nvalid);
2244 	        } else {
2245 	          wcsfprintf(stderr, "%.80s\n  Accepted (%d) as a "
2246 	            "valid WCS keyrecord.\n", keyrec, nvalid);
2247 	        }
2248 	      }
2249 
2250 	      BEGIN(FLUSH);
2251 
2252 	    } else {
2253 	      errmsg = "syntactically valid WCS keyrecord has no effect";
2254 	      BEGIN(ERROR);
2255 	    }
2256 
2257 	  } else {
2258 	    BEGIN(FLUSH);
2259 	  }
2260 	}
2261 
2262 <COMMENT>.*" "*\/.*$ {
2263 	  errmsg = "invalid keyvalue";
2264 	  BEGIN(ERROR);
2265 	}
2266 
2267 <COMMENT>[^ \/\n]*{INLINE}$ {
2268 	  errmsg = "invalid keyvalue";
2269 	  BEGIN(ERROR);
2270 	}
2271 
2272 <COMMENT>" "+[^\/\n].*{INLINE}$ {
2273 	  errmsg = "invalid keyvalue or malformed keycomment";
2274 	  BEGIN(ERROR);
2275 	}
2276 
2277 <COMMENT>.*$ {
2278 	  errmsg = "malformed keycomment";
2279 	  BEGIN(ERROR);
2280 	}
2281 
2282 <DISCARD>.*$ {
2283 	  if (ipass == npass) {
2284 	    if (ctrl < 0) {
2285 	      // Preserve discards.
2286 	      keep = keyrec;
2287 
2288 	    } else if (2 < ctrl) {
2289 	      nother++;
2290 	      wcsfprintf(stderr, "%.80s\n  Not a recognized WCS keyword.\n",
2291 	        keyrec);
2292 	    }
2293 	  }
2294 	  BEGIN(FLUSH);
2295 	}
2296 
2297 <ERROR>.*$ {
2298 	  if (ipass == npass) {
2299 	    (*nreject)++;
2300 
2301 	    if (ctrl%10 == -1) {
2302 	      // Preserve rejects.
2303 	      keep = keyrec;
2304 	    }
2305 
2306 	    if (1 < abs(ctrl%10)) {
2307 	      wcsfprintf(stderr, "%.80s\n  Rejected (%d), %s.\n",
2308 	        keyrec, *nreject, errmsg);
2309 	    }
2310 	  }
2311 	  BEGIN(FLUSH);
2312 	}
2313 
2314 <FLUSH>.*\n {
2315 	  if (ipass == npass && keep) {
2316 	    if (hptr < keep) {
2317 	      strncpy(hptr, keep, 80);
2318 	    }
2319 	    hptr += 80;
2320 	  }
2321 
2322 	  naux += auxprm;
2323 
2324 	  // Throw away the rest of the line and reset for the next one.
2325 	  i = j = 0;
2326 	  m = 0;
2327 	  a = ' ';
2328 
2329 	  keyrec += 80;
2330 
2331 	  valtype = -1;
2332 	  distype =  0;
2333 	  vptr    = 0x0;
2334 	  keep    = 0x0;
2335 
2336 	  altlin  = 0;
2337 	  npptr   = 0x0;
2338 	  chekval = 0x0;
2339 	  special = 0x0;
2340 	  auxprm  = 0;
2341 	  sipflag = 0;
2342 	  dssflag = 0;
2343 	  watflag = 0;
2344 
2345 	  BEGIN(INITIAL);
2346 	}
2347 
2348 <<EOF>>	 {
2349 	  // End-of-input.
2350 	  int status;
2351 	  if (ipass == 1) {
2352 	    if ((status = wcspih_init1(naxis, alts, dpq, npv, nps, ndp, ndq,
2353 	                               naux, distran, nwcs, wcs)) ||
2354 	        (*nwcs == 0 && ctrl == 0)) {
2355 	      return status;
2356 	    }
2357 
2358 	    if (2 < abs(ctrl%10)) {
2359 	      if (*nwcs == 1) {
2360 	        if (strcmp(wcs[0]->wcsname, "DEFAULTS") != 0) {
2361 	          wcsfprintf(stderr, "Found one coordinate representation.\n");
2362 	        }
2363 	      } else {
2364 	        wcsfprintf(stderr, "Found %d coordinate representations.\n",
2365 	          *nwcs);
2366 	      }
2367 	    }
2368 
2369 	    watstr = calloc(2*(watn*68 + 1), sizeof(char));
2370 	    wat[0] = watstr;
2371 	    wat[1] = watstr + watn*68 + 1;
2372 	  }
2373 
2374 	  if (ipass++ < npass) {
2375 	    yyextra->hdr = header;
2376 	    yyextra->nkeyrec = nkeyrec;
2377 	    keyrec = header;
2378 	    *nreject = 0;
2379 
2380 	    i = j = 0;
2381 	    m = 0;
2382 	    a = ' ';
2383 
2384 	    valtype = -1;
2385 	    distype =  0;
2386 	    vptr    = 0x0;
2387 
2388 	    altlin  = 0;
2389 	    npptr   = 0x0;
2390 	    chekval = 0x0;
2391 	    special = 0x0;
2392 	    auxprm  = 0;
2393 	    sipflag = 0;
2394 	    dssflag = 0;
2395 	    watflag = 0;
2396 
2397 	    yyrestart(yyin, yyscanner);
2398 
2399 	  } else {
2400 
2401 	    if (ctrl < 0) {
2402 	      *hptr = '\0';
2403 	    } else if (ctrl == 1) {
2404 	      wcsfprintf(stderr, "%d WCS keyrecord%s rejected.\n",
2405 	        *nreject, (*nreject==1)?" was":"s were");
2406 	    } else if (ctrl == 4) {
2407 	      wcsfprintf(stderr, "\n");
2408 	      wcsfprintf(stderr, "%5d keyrecord%s rejected for syntax or "
2409 	        "other errors,\n", *nreject, (*nreject==1)?" was":"s were");
2410 	      wcsfprintf(stderr, "%5d %s recognized as syntactically valid, "
2411 	        "and\n", nvalid, (nvalid==1)?"was":"were");
2412 	      wcsfprintf(stderr, "%5d other%s were not recognized as WCS "
2413 	        "keyrecords.\n", nother, (nother==1)?"":"s");
2414 	    }
2415 
2416 	    status = wcspih_final(ndp, ndq, distran, dsstmp, wat, nwcs, wcs);
2417 	    free(watstr);
2418 	    return status;
2419 	  }
2420 	}
2421 
2422 %%
2423 
2424 /*----------------------------------------------------------------------------
2425 * External interface to the scanner.
2426 *---------------------------------------------------------------------------*/
2427 
2428 int wcspih(
2429   char *header,
2430   int nkeyrec,
2431   int relax,
2432   int ctrl,
2433   int *nreject,
2434   int *nwcs,
2435   struct wcsprm **wcs)
2436 
2437 {
2438   // Function prototypes.
2439   int yylex_init_extra(YY_EXTRA_TYPE extra, yyscan_t *yyscanner);
2440   int yylex_destroy(yyscan_t yyscanner);
2441 
2442   struct wcspih_extra extra;
2443   yyscan_t yyscanner;
2444   yylex_init_extra(&extra, &yyscanner);
2445   int status = wcspih_scanner(header, nkeyrec, relax, ctrl, nreject, nwcs,
2446                               wcs, yyscanner);
2447   yylex_destroy(yyscanner);
2448 
2449   return status;
2450 }
2451 
2452 
2453 /*----------------------------------------------------------------------------
2454 * Determine the number of coordinate representations (up to 27) and the
2455 * number of coordinate axes in each, which distortions are present, and the
2456 * number of PVi_ma, PSi_ma, DPja, and DQia keywords in each representation.
2457 *---------------------------------------------------------------------------*/
2458 
2459 void wcspih_pass1(
2460   int naxis,
2461   int i,
2462   int j,
2463   char a,
2464   int distype,
2465   int alts[],
2466   int dpq[],
2467   int *npptr)
2468 
2469 {
2470   // On the first pass alts[] is used to determine the number of axes
2471   // for each of the 27 possible alternate coordinate descriptions.
2472   if (a == 0) {
2473     return;
2474   }
2475 
2476   int ialt = 0;
2477   if (a != ' ') {
2478     ialt = a - 'A' + 1;
2479   }
2480 
2481   int *ip = alts + ialt;
2482 
2483   if (*ip < naxis) {
2484     *ip = naxis;
2485   }
2486 
2487   // i or j can be greater than naxis.
2488   if (*ip < i) {
2489     *ip = i;
2490   }
2491 
2492   if (*ip < j) {
2493     *ip = j;
2494   }
2495 
2496   // Type of distortions present.
2497   dpq[ialt] |= distype;
2498 
2499   // Count PVi_ma, PSi_ma, DPja, or DQia keywords.
2500   if (npptr) {
2501     npptr[ialt]++;
2502   }
2503 }
2504 
2505 
2506 /*----------------------------------------------------------------------------
2507 * Allocate memory for an array of the required number of wcsprm structs and
2508 * initialize each of them.
2509 *---------------------------------------------------------------------------*/
2510 
2511 int wcspih_init1(
2512   int naxis,
2513   int alts[],
2514   int dpq[],
2515   int npv[],
2516   int nps[],
2517   int ndp[],
2518   int ndq[],
2519   int naux,
2520   int distran,
2521   int *nwcs,
2522   struct wcsprm **wcs)
2523 
2524 {
2525   int status = 0;
2526 
2527   // Find the number of coordinate descriptions.
2528   *nwcs = 0;
2529   for (int ialt = 0; ialt < 27; ialt++) {
2530     if (alts[ialt]) (*nwcs)++;
2531   }
2532 
2533   int defaults;
2534   if ((defaults = !(*nwcs) && naxis)) {
2535     // NAXIS is non-zero but there were no WCS keywords with an alternate
2536     // version code; create a default WCS with blank alternate version.
2537     wcspih_pass1(naxis, 0, 0, ' ', 0, alts, dpq, 0x0);
2538     *nwcs = 1;
2539   }
2540 
2541   if (*nwcs) {
2542     // Allocate memory for the required number of wcsprm structs.
2543     if ((*wcs = calloc(*nwcs, sizeof(struct wcsprm))) == 0x0) {
2544       return WCSHDRERR_MEMORY;
2545     }
2546 
2547     int ndis = 0;
2548     if (distran == SIP) {
2549       // DPja.NAXES and DPja.OFFSET.j to be added for SIP (see below and
2550       // wcspih_final()).
2551       ndp[0] += 6;
2552 
2553     } else if (distran == DSS) {
2554       // DPja.NAXES to be added for DSS (see below and wcspih_final()).
2555       ndq[0] += 2;
2556     }
2557 
2558     // Initialize each wcsprm struct.
2559     struct wcsprm *wcsp = *wcs;
2560     *nwcs = 0;
2561     for (int ialt = 0; ialt < 27; ialt++) {
2562       if (alts[ialt]) {
2563         wcsp->flag = -1;
2564         int npvmax = npv[ialt];
2565         int npsmax = nps[ialt];
2566         if ((status = wcsinit(1, alts[ialt], wcsp, npvmax, npsmax, -1))) {
2567           wcsvfree(nwcs, wcs);
2568           break;
2569         }
2570 
2571         // Record the alternate version code.
2572         if (ialt) {
2573           wcsp->alt[0] = 'A' + ialt - 1;
2574         }
2575 
2576         // Record in wcsname whether this is a default description.
2577         if (defaults) {
2578           strcpy(wcsp->wcsname, "DEFAULTS");
2579         }
2580 
2581         // Any additional auxiliary keywords present?
2582         if (naux) {
2583           if (wcsauxi(1, wcsp)) {
2584             return WCSHDRERR_MEMORY;
2585           }
2586         }
2587 
2588         // Any distortions present?
2589         struct disprm *disp;
2590         if (dpq[ialt] & 1) {
2591           if ((disp = calloc(1, sizeof(struct disprm))) == 0x0) {
2592             return WCSHDRERR_MEMORY;
2593           }
2594 
2595           // Attach it to linprm.  Also inits it.
2596           ndis++;
2597           int ndpmax = ndp[ialt];
2598           disp->flag = -1;
2599           lindist(1, &(wcsp->lin), disp, ndpmax);
2600         }
2601 
2602         if (dpq[ialt] & 2) {
2603           if ((disp = calloc(1, sizeof(struct disprm))) == 0x0) {
2604             return WCSHDRERR_MEMORY;
2605           }
2606 
2607           // Attach it to linprm.  Also inits it.
2608           ndis++;
2609           int ndpmax = ndq[ialt];
2610           disp->flag = -1;
2611           lindist(2, &(wcsp->lin), disp, ndpmax);
2612         }
2613 
2614         // On the second pass alts[] indexes the array of wcsprm structs.
2615         alts[ialt] = (*nwcs)++;
2616 
2617         wcsp++;
2618 
2619       } else {
2620         // Signal that there is no wcsprm for this alt.
2621         alts[ialt] = -1;
2622       }
2623     }
2624 
2625 
2626     // Translated distortion?  Neither SIP nor DSS have alternates, so the
2627     // presence of keywords for either (not both together), as flagged by
2628     // distran, necessarily refers to the primary representation.
2629     if (distran == SIP) {
2630       strcpy((*wcs)->lin.dispre->dtype[0], "SIP");
2631       strcpy((*wcs)->lin.dispre->dtype[1], "SIP");
2632 
2633       // SIP doesn't have axis mapping.
2634       (*wcs)->lin.dispre->ndp = 6;
2635       dpfill((*wcs)->lin.dispre->dp,   "DP1", "NAXES",  0, 0, 2, 0.0);
2636       dpfill((*wcs)->lin.dispre->dp+3, "DP2", "NAXES",  0, 0, 2, 0.0);
2637 
2638     } else if (distran == DSS) {
2639       strcpy((*wcs)->lin.disseq->dtype[0], "DSS");
2640       strcpy((*wcs)->lin.disseq->dtype[1], "DSS");
2641 
2642       // The Paper IV translation of DSS doesn't require an axis mapping.
2643       (*wcs)->lin.disseq->ndp = 2;
2644       dpfill((*wcs)->lin.disseq->dp,   "DQ1", "NAXES",  0, 0, 2, 0.0);
2645       dpfill((*wcs)->lin.disseq->dp+1, "DQ2", "NAXES",  0, 0, 2, 0.0);
2646     }
2647   }
2648 
2649   return status;
2650 }
2651 
2652 
2653 /*----------------------------------------------------------------------------
2654 * Interpret the JDREF, JDREFI, and JDREFF keywords.
2655 *---------------------------------------------------------------------------*/
2656 
2657 int wcspih_jdref(double *mjdref, const double *jdref)
2658 
2659 {
2660   // Set MJDREF from JDREF.
2661   if (undefined(mjdref[0] && undefined(mjdref[1]))) {
2662     mjdref[0] = jdref[0] - 2400000.0;
2663     mjdref[1] = jdref[1] - 0.5;
2664 
2665     if (mjdref[1] < 0.0) {
2666       mjdref[0] -= 1.0;
2667       mjdref[1] += 1.0;
2668     }
2669   }
2670 
2671   return 0;
2672 }
2673 
2674 int wcspih_jdrefi(double *mjdref, const double *jdrefi)
2675 
2676 {
2677   // Set the integer part of MJDREF from JDREFI.
2678   if (undefined(mjdref[0])) {
2679     mjdref[0] = *jdrefi - 2400000.5;
2680   }
2681 
2682   return 0;
2683 }
2684 
2685 
2686 int wcspih_jdreff(double *mjdref, const double *jdreff)
2687 
2688 {
2689   // Set the fractional part of MJDREF from JDREFF.
2690   if (undefined(mjdref[1])) {
2691     mjdref[1] = *jdreff;
2692   }
2693 
2694   return 0;
2695 }
2696 
2697 
2698 /*----------------------------------------------------------------------------
2699 * Interpret EPOCHa keywords.
2700 *---------------------------------------------------------------------------*/
2701 
2702 int wcspih_epoch(double *equinox, const double *epoch)
2703 
2704 {
2705   // If EQUINOXa is currently undefined then set it from EPOCHa.
2706   if (undefined(*equinox)) {
2707     *equinox = *epoch;
2708   }
2709 
2710   return 0;
2711 }
2712 
2713 
2714 /*----------------------------------------------------------------------------
2715 * Interpret VSOURCEa keywords.
2716 *---------------------------------------------------------------------------*/
2717 
2718 int wcspih_vsource(double *zsource, const double *vsource)
2719 
2720 {
2721   const double c = 299792458.0;
2722 
2723   // If ZSOURCEa is currently undefined then set it from VSOURCEa.
2724   if (undefined(*zsource)) {
2725     // Convert relativistic Doppler velocity to redshift.
2726     double beta = *vsource/c;
2727     *zsource = (1.0 + beta)/sqrt(1.0 - beta*beta) - 1.0;
2728   }
2729 
2730   return 0;
2731 }
2732 
2733 
2734 /*----------------------------------------------------------------------------
2735 * Check validity of a TIMEPIXR keyvalue.
2736 *---------------------------------------------------------------------------*/
2737 
2738 int wcspih_timepixr(double timepixr)
2739 
2740 {
2741   return (timepixr < 0.0 || 1.0 < timepixr);
2742 }
2743 
2744 
2745 /*----------------------------------------------------------------------------
2746 * Interpret special keywords encountered for each coordinate representation.
2747 *---------------------------------------------------------------------------*/
2748 
2749 int wcspih_final(
2750   int ndp[],
2751   int ndq[],
2752   int distran,
2753   double dsstmp[],
2754   char *wat[],
2755   int  *nwcs,
2756   struct wcsprm **wcs)
2757 
2758 {
2759   for (int ialt = 0; ialt < *nwcs; ialt++) {
2760     // Interpret -TAB header keywords.
2761     int status;
2762     if ((status = wcstab(*wcs+ialt))) {
2763        wcsvfree(nwcs, wcs);
2764        return status;
2765     }
2766 
2767     if (ndp[ialt] && ndq[ialt]) {
2768       // Prior and sequent distortions co-exist in this representation;
2769       // ensure the latter gets DVERRa.
2770       (*wcs+ialt)->lin.disseq->totdis = (*wcs+ialt)->lin.dispre->totdis;
2771     }
2772   }
2773 
2774   // Translated distortion functions; apply only to the primary WCS.
2775   struct wcsprm *wcsp = *wcs;
2776   if (distran == SIP) {
2777     // SIP doesn't have alternates, nor axis mapping.
2778     struct disprm *disp = wcsp->lin.dispre;
2779     dpfill(disp->dp+1, "DP1", "OFFSET.1",  0, 1, 0, wcsp->crpix[0]);
2780     dpfill(disp->dp+2, "DP1", "OFFSET.2",  0, 1, 0, wcsp->crpix[1]);
2781     dpfill(disp->dp+4, "DP2", "OFFSET.1",  0, 1, 0, wcsp->crpix[0]);
2782     dpfill(disp->dp+5, "DP2", "OFFSET.2",  0, 1, 0, wcsp->crpix[1]);
2783 
2784   } else if (distran == DSS) {
2785     // DSS doesn't have alternates, nor axis mapping.  This translation
2786     // follows Paper IV, Sect. 5.2 using the same variable names.
2787     double CNPIX1 = dsstmp[0];
2788     double CNPIX2 = dsstmp[1];
2789 
2790     double Xc = dsstmp[2]/1000.0;
2791     double Yc = dsstmp[3]/1000.0;
2792     double Rx = dsstmp[4]/1000.0;
2793     double Ry = dsstmp[5]/1000.0;
2794 
2795     double A1 = dsstmp[14];
2796     double A2 = dsstmp[15];
2797     double A3 = dsstmp[16];
2798     double B1 = dsstmp[17];
2799     double B2 = dsstmp[18];
2800     double B3 = dsstmp[19];
2801     double S  = sqrt(fabs(A1*B1 - A2*B2));
2802 
2803     double X0 = (A2*B3 - A3*B1) / (A1*B1 - A2*B2);
2804     double Y0 = (A3*B2 - A1*B3) / (A1*B1 - A2*B2);
2805 
2806     wcsp->crpix[0] = (Xc - X0)/Rx - (CNPIX1 - 0.5);
2807     wcsp->crpix[1] = (Yc + Y0)/Ry - (CNPIX2 - 0.5);
2808 
2809     wcsp->pc[0] =  A1*Rx/S;
2810     wcsp->pc[1] = -A2*Ry/S;
2811     wcsp->pc[2] = -B2*Rx/S;
2812     wcsp->pc[3] =  B1*Ry/S;
2813     wcsp->altlin = 1;
2814 
2815     wcsp->cdelt[0] = -S/3600.0;
2816     wcsp->cdelt[1] =  S/3600.0;
2817 
2818     double *crval = wcsp->crval;
2819     crval[0] = (dsstmp[6]  + (dsstmp[7]  + dsstmp[8] /60.0)/60.0)*15.0;
2820     crval[1] =  dsstmp[10] + (dsstmp[11] + dsstmp[12]/60.0)/60.0;
2821     if (dsstmp[9] == -1.0) crval[1] *= -1.0;
2822 
2823     strcpy(wcsp->ctype[0], "RA---TAN");
2824     strcpy(wcsp->ctype[1], "DEC--TAN");
2825 
2826     sprintf(wcsp->wcsname, "DSS PLATEID %.4s", (char *)(dsstmp+13));
2827 
2828     // Erase the approximate WCS provided in modern DSS headers.
2829     wcsp->cd[0] = 0.0;
2830     wcsp->cd[1] = 0.0;
2831     wcsp->cd[2] = 0.0;
2832     wcsp->cd[3] = 0.0;
2833 
2834   } else if (distran == WAT) {
2835     // TNX and ZPX don't have alternates, nor axis mapping.
2836     char *wp;
2837     int  omax, omin, wctrl[4];
2838     double wval;
2839     struct disprm *disp = wcsp->lin.disseq;
2840 
2841     // Disassemble the core dump stored in the WATi_m strings.
2842     int i, nterms = 0;
2843     for (i = 0; i < 2; i++) {
2844       char wtype[8];
2845       sscanf(wat[i], "wtype=%s", wtype);
2846 
2847       if (strcmp(wtype, "tnx") == 0) {
2848         strcpy(disp->dtype[i], "WAT-TNX");
2849       } else if (strcmp(wtype, "zpx") == 0) {
2850         strcpy(disp->dtype[i], "WAT-ZPX");
2851       } else {
2852         // Could contain "tan" or something else to be ignored.
2853         lindist(2, &(wcsp->lin), 0x0, 0);
2854         return 0;
2855       }
2856 
2857       // The PROJPn parameters are duplicated on each ZPX axis.
2858       if (i == 1 && strcmp(wtype, "zpx") == 0) {
2859         // Take those on the second (latitude) axis ignoring the other.
2860         // First we have to count them and allocate space in wcsprm.
2861         wp = wat[i];
2862 	int npv;
2863         for (npv = 0; npv < 30; npv++) {
2864           if ((wp = strstr(wp, "projp")) == 0x0) break;
2865           wp += 5;
2866         }
2867 
2868         // Allocate space.
2869         if (npv) {
2870           wcsp->npvmax += npv;
2871           wcsp->pv = realloc(wcsp->pv, wcsp->npvmax*sizeof(struct pvcard));
2872           if (wcsp->pv == 0x0) {
2873             return WCSHDRERR_MEMORY;
2874           }
2875 
2876           wcsp->m_pv = wcsp->pv;
2877         }
2878 
2879         // Copy the values.
2880         wp = wat[i];
2881         for (int ipv = wcsp->npv; ipv < wcsp->npvmax; ipv++) {
2882           if ((wp = strstr(wp, "projp")) == 0x0) break;
2883 
2884           int m;
2885           sscanf(wp, "projp%d=%lf", &m, &wval);
2886           wcsp->pv[ipv].i = 2;
2887           wcsp->pv[ipv].m = m;
2888           wcsp->pv[ipv].value = wval;
2889 
2890           wp += 5;
2891         }
2892 
2893         wcsp->npv += npv;
2894       }
2895 
2896       // Read the control parameters.
2897       if ((wp = strchr(wat[i], '"')) == 0x0) {
2898         return WCSHDRERR_PARSER;
2899       }
2900       wp++;
2901 
2902       for (int m = 0; m < 4; m++) {
2903         sscanf(wp, "%d", wctrl+m);
2904         if ((wp = strchr(wp, ' ')) == 0x0) {
2905           return WCSHDRERR_PARSER;
2906         }
2907         wp++;
2908       }
2909 
2910       // How many coefficients are we expecting?
2911       omin = (wctrl[1] < wctrl[2]) ? wctrl[1] : wctrl[2];
2912       omax = (wctrl[1] < wctrl[2]) ? wctrl[2] : wctrl[1];
2913       if (wctrl[3] == 0) {
2914         // No cross terms.
2915         nterms += omin + omax;
2916 
2917       } else if (wctrl[3] == 1) {
2918         // Full cross terms.
2919         nterms += omin*omax;
2920 
2921       } else if (wctrl[3] == 2) {
2922         // Half cross terms.
2923         nterms += omin*omax - omin*(omin-1)/2;
2924       }
2925     }
2926 
2927     // Allocate memory for dpkeys.
2928     ndq[0] += 2*(1 + 1 + 4) + nterms;
2929 
2930     disp->ndpmax += ndq[0];
2931     disp->dp = realloc(disp->dp, disp->ndpmax*sizeof(struct dpkey));
2932     if (disp->dp == 0x0) {
2933       return WCSHDRERR_MEMORY;
2934     }
2935 
2936     disp->m_dp = disp->dp;
2937 
2938 
2939     // Populate dpkeys.
2940     int idp = disp->ndp;
2941     for (i = 0; i < 2; i++) {
2942       dpfill(disp->dp+(idp++), "DQ", "NAXES", i+1, 0, 2, 0.0);
2943 
2944       // Read the control parameters.
2945       if ((wp = strchr(wat[i], '"')) == 0x0) {
2946         return WCSHDRERR_PARSER;
2947       }
2948       wp++;
2949 
2950       for (int m = 0; m < 4; m++) {
2951         sscanf(wp, "%d", wctrl+m);
2952         if ((wp = strchr(wp, ' ')) == 0x0) {
2953           return WCSHDRERR_PARSER;
2954         }
2955         wp++;
2956       }
2957 
2958       // Polynomial type.
2959       char wpoly[12];
2960       dpfill(disp->dp+(idp++), "DQ", "WAT.POLY", i+1, 0, wctrl[0], 0.0);
2961       if (wctrl[0] == 1) {
2962         // Chebyshev polynomial.
2963         strcpy(wpoly, "CHBY");
2964       } else if (wctrl[0] == 2) {
2965         // Legendre polynomial.
2966         strcpy(wpoly, "LEGR");
2967       } else if (wctrl[0] == 3) {
2968         // Polynomial is the sum of monomials.
2969         strcpy(wpoly, "MONO");
2970       } else {
2971         // Unknown code.
2972         strcpy(wpoly, "UNKN");
2973       }
2974 
2975       // Read the scaling parameters.
2976       char field[40];
2977       for (int m = 0; m < 4; m++) {
2978         sscanf(wp, "%lf", &wval);
2979         sprintf(field, "WAT.%c%s", (m<2)?'X':'Y', (m%2)?"MAX":"MIN");
2980         dpfill(disp->dp+(idp++), "DQ", field, i+1, 1, 0, wval);
2981 
2982         if ((wp = strchr(wp, ' ')) == 0x0) {
2983           return WCSHDRERR_PARSER;
2984         }
2985         wp++;
2986       }
2987 
2988       // Read the coefficients.
2989       for (int n = 0; n < wctrl[2]; n++) {
2990         for (int m = 0; m < wctrl[1]; m++) {
2991           if (wctrl[3] == 0) {
2992             if (m && n) continue;
2993           } else if (wctrl[3] == 2) {
2994             if (m+n > omax-1) continue;
2995           }
2996 
2997           sscanf(wp, "%lf", &wval);
2998           if (wval == 0.0) continue;
2999 
3000           sprintf(field, "WAT.%s.%d_%d", wpoly, m, n);
3001           dpfill(disp->dp+(idp++), "DQ", field, i+1, 1, 0, wval);
3002 
3003           if ((wp = strchr(wp, ' ')) == 0x0) {
3004             return WCSHDRERR_PARSER;
3005           }
3006           wp++;
3007         }
3008       }
3009     }
3010 
3011     disp->ndp = idp;
3012   }
3013 
3014   return 0;
3015 }
3016