SINFONI Pipeline Reference Manual  2.6.0
irplib_sdp_spectrum.c
1 /*
2  * This file is part of the ESO Common Pipeline Library
3  * Copyright (C) 2014 European Southern Observatory
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23 
24 /*-----------------------------------------------------------------------------
25  Includes
26  -----------------------------------------------------------------------------*/
27 
28 #include <complex.h>
29 #include <string.h>
30 #include <assert.h>
31 #include <stdlib.h>
32 #include <limits.h>
33 #include <sys/types.h>
34 #include <regex.h>
35 #include <ctype.h>
36 #include "irplib_sdp_spectrum.h"
37 
38 #ifdef IRPLIB_USE_FITS_UPDATE_CHECKSUM
39 # include <fitsio2.h>
40 #endif
41 
42 /*-----------------------------------------------------------------------------
43  Defines
44  -----------------------------------------------------------------------------*/
45 
46 /* Manually setup the following while this code remains outside CPL proper: */
47 #ifndef cpl_error_set_regex
48 
49 cpl_error_code
50 cpl_error_set_regex_macro(const char *, cpl_error_code, int,
51  const regex_t *, const char *,
52  unsigned, const char *, ...) CPL_ATTR_PRINTF(7,8);
53 
54 #define cpl_error_set_regex(code, regcode, preg, ...) \
55  cpl_error_set_regex_macro(cpl_func, code, regcode, preg, \
56  __FILE__, __LINE__, __VA_ARGS__)
57 
58 #endif /* cpl_error_set_regex */
59 
60 
61 #define KEY_ARCFILE "ARCFILE"
62 #define KEY_ORIGFILE "ORIGFILE"
63 #define KEY_RA "RA"
64 #define KEY_RA_COMMENT "[deg] Spectroscopic target position (J2000)"
65 #define KEY_DEC "DEC"
66 #define KEY_DEC_COMMENT "[deg] Spectroscopic target position (J2000)"
67 #define KEY_EXPTIME "EXPTIME"
68 #define KEY_EXPTIME_COMMENT "[s] Total integration time per pixel"
69 #define KEY_TEXPTIME "TEXPTIME"
70 #define KEY_TEXPTIME_COMMENT "[s] Total integration time of all exposures"
71 #define KEY_TIMESYS "TIMESYS"
72 #define KEY_TIMESYS_COMMENT "Time system used"
73 #define KEY_MJDOBS "MJD-OBS"
74 #define KEY_MJDOBS_COMMENT "[d] Start of observations (days)"
75 #define KEY_MJDEND "MJD-END"
76 #define KEY_MJDEND_COMMENT "[d] End of observations (days)"
77 #define KEY_PRODLVL "PRODLVL"
78 #define KEY_PRODLVL_VALUE 2
79 #define KEY_PRODLVL_COMMENT "Phase 3 product level: 1-raw, 2-science grade, 3-advanced"
80 #define KEY_PROCSOFT "PROCSOFT"
81 #define KEY_PROCSOFT_COMMENT "ESO pipeline version"
82 #define KEY_PRODCATG "PRODCATG"
83 #define KEY_PRODCATG_COMMENT "Data product category"
84 #define KEY_ORIGIN "ORIGIN"
85 #define KEY_ORIGIN_VALUE "ESO"
86 #define KEY_ORIGIN_COMMENT "European Southern Observatory"
87 #define KEY_EXT_OBJ "EXT_OBJ"
88 #define KEY_EXT_OBJ_COMMENT "TRUE if extended"
89 #define KEY_DISPELEM "DISPELEM"
90 #define KEY_DISPELEM_COMMENT "Dispersive element name"
91 #define KEY_SPECSYS "SPECSYS"
92 #define KEY_SPECSYS_VALUE "TOPOCENT"
93 #define KEY_SPECSYS_COMMENT "Reference frame for spectral coordinates"
94 #define KEY_PROG_ID "PROG_ID"
95 #define KEY_PROG_ID_COMMENT "ESO programme identification"
96 #define KEY_OBID "OBID"
97 #define KEY_OBID_COMMENT "Observation block ID"
98 #define KEY_M_EPOCH "M_EPOCH"
99 #define KEY_M_EPOCH_COMMENT "TRUE if resulting from multiple epochs"
100 #define KEY_OBSTECH "OBSTECH"
101 #define KEY_OBSTECH_COMMENT "Technique for observation"
102 #define KEY_FLUXCAL "FLUXCAL"
103 #define KEY_FLUXCAL_COMMENT "Type of flux calibration (ABSOLUTE or UNCALIBRATED)"
104 #define KEY_CONTNORM "CONTNORM"
105 #define KEY_CONTNORM_COMMENT "TRUE if normalised to the continuum"
106 #define KEY_WAVELMIN "WAVELMIN"
107 #define KEY_WAVELMIN_COMMENT "[nm] Minimum wavelength"
108 #define KEY_WAVELMAX "WAVELMAX"
109 #define KEY_WAVELMAX_COMMENT "[nm] Maximum wavelength"
110 #define KEY_SPEC_BIN "SPEC_BIN"
111 #define KEY_SPEC_BIN_COMMENT "[nm] Wavelength bin size"
112 #define KEY_TOT_FLUX "TOT_FLUX"
113 #define KEY_TOT_FLUX_COMMENT "TRUE if photometric conditions and all source flux is captured"
114 #define KEY_FLUXERR "FLUXERR"
115 #define KEY_FLUXERR_VALUE -2
116 #define KEY_FLUXERR_COMMENT "Uncertainty in flux scale (%)"
117 #define KEY_REFERENC "REFERENC"
118 #define KEY_REFERENC_VALUE " "
119 #define KEY_REFERENC_COMMENT "Reference publication"
120 #define KEY_SPEC_RES "SPEC_RES"
121 #define KEY_SPEC_RES_COMMENT "Reference spectral resolving power"
122 #define KEY_SPEC_ERR "SPEC_ERR"
123 #define KEY_SPEC_ERR_COMMENT "[nm] Statistical error in spectral coordinate"
124 #define KEY_SPEC_SYE "SPEC_SYE"
125 #define KEY_SPEC_SYE_COMMENT "[nm] Systematic error in spectral coordinate"
126 #define KEY_LAMNLIN "LAMNLIN"
127 #define KEY_LAMNLIN_COMMENT "Number of arc lines used for the wavel. solution"
128 #define KEY_LAMRMS "LAMRMS"
129 #define KEY_LAMRMS_COMMENT "[nm] RMS of the residuals of the wavel. solution"
130 #define KEY_GAIN "GAIN"
131 #define KEY_GAIN_COMMENT "Conversion factor (e-/ADU) electrons per data unit"
132 #define KEY_DETRON "DETRON"
133 #define KEY_DETRON_COMMENT "Readout noise per output (e-)"
134 #define KEY_EFFRON "EFFRON"
135 #define KEY_EFFRON_COMMENT "Median effective readout noise (e-)"
136 #define KEY_SNR "SNR"
137 #define KEY_SNR_COMMENT "Median signal to noise ratio per order"
138 #define KEY_NCOMBINE "NCOMBINE"
139 #define KEY_NCOMBINE_COMMENT "No. of combined raw science data files"
140 #define KEY_PROV "PROV"
141 #define KEY_PROV_COMMENT "Originating raw science file"
142 #define KEY_ASSON "ASSON"
143 #define KEY_ASSON_COMMENT "Associated file name"
144 #define KEY_ASSOC "ASSOC"
145 #define KEY_ASSOC_COMMENT "Associated file category"
146 #define KEY_ASSOM "ASSOM"
147 #define KEY_ASSOM_COMMENT "Associated file md5sum"
148 #define KEY_VOCLASS "VOCLASS"
149 #define KEY_VOCLASS_VALUE "SPECTRUM V2.0"
150 #define KEY_VOCLASS_COMMENT "VO Data Model"
151 #define KEY_VOPUB "VOPUB"
152 #define KEY_VOPUB_VALUE "ESO/SAF"
153 #define KEY_VOPUB_COMMENT "VO Publishing Authority"
154 #define KEY_TITLE "TITLE"
155 #define KEY_TITLE_COMMENT "Dataset title"
156 #define KEY_OBJECT "OBJECT"
157 #define KEY_OBJECT_COMMENT "Target designation"
158 #define KEY_OBJECT_PHDU_COMMENT "Original target."
159 #define KEY_APERTURE "APERTURE"
160 #define KEY_APERTURE_COMMENT "[deg] Aperture diameter"
161 #define KEY_TELAPSE "TELAPSE"
162 #define KEY_TELAPSE_COMMENT "[s] Total elapsed time"
163 #define KEY_TMID "TMID"
164 #define KEY_TMID_COMMENT "[d] MJD mid exposure"
165 #define KEY_SPEC_VAL "SPEC_VAL"
166 #define KEY_SPEC_VAL_COMMENT "[nm] Mean wavelength"
167 #define KEY_SPEC_BW "SPEC_BW"
168 #define KEY_SPEC_BW_COMMENT "[nm] Bandpass width = Wmax - Wmin"
169 #define KEY_TDMIN(n) "TDMIN"#n
170 #define KEY_TDMIN1_COMMENT "Start in spectral coordinate"
171 #define KEY_TDMAX(n) "TDMAX"#n
172 #define KEY_TDMAX1_COMMENT "Stop in spectral coordinate"
173 #define KEY_TUTYP "TUTYP"
174 #define KEY_TUTYP_COMMENT "IVOA data model element for field "
175 #define KEY_TUCD "TUCD"
176 #define KEY_TUCD_COMMENT "UCD for field "
177 #define KEY_TCOMM "TCOMM"
178 #define KEY_TCOMM_COMMENT "Description for field "
179 #define KEY_NELEM "NELEM"
180 #define KEY_NELEM_COMMENT "Length of the data arrays"
181 #define KEY_EXTNAME "EXTNAME"
182 #define KEY_EXTNAME_VALUE "SPECTRUM"
183 #define KEY_EXTNAME_COMMENT "Extension name"
184 #define KEY_INHERIT "INHERIT"
185 #define KEY_INHERIT_VALUE CPL_TRUE
186 #define KEY_INHERIT_COMMENT "Primary header keywords are inherited"
187 
188 /* A regular expression to select all keywords relevant to a spectrum class. */
189 #define ALL_KEYS_REGEXP \
190  "^(" KEY_RA "|" \
191  KEY_DEC "|" \
192  KEY_EXPTIME "|" \
193  KEY_TEXPTIME "|" \
194  KEY_TIMESYS "|" \
195  KEY_MJDOBS "|" \
196  KEY_MJDEND "|" \
197  KEY_PRODLVL "|" \
198  KEY_PROCSOFT "|" \
199  KEY_PRODCATG "|" \
200  KEY_ORIGIN "|" \
201  KEY_EXT_OBJ "|" \
202  KEY_DISPELEM "|" \
203  KEY_SPECSYS "|" \
204  KEY_PROG_ID "|" \
205  KEY_OBID "[0-9]+|" \
206  KEY_M_EPOCH "|" \
207  KEY_OBSTECH "|" \
208  KEY_FLUXCAL "|" \
209  KEY_CONTNORM "|" \
210  KEY_WAVELMIN "|" \
211  KEY_WAVELMAX "|" \
212  KEY_SPEC_BIN "|" \
213  KEY_TOT_FLUX "|" \
214  KEY_FLUXERR "|" \
215  KEY_REFERENC "|" \
216  KEY_SPEC_RES "|" \
217  KEY_SPEC_ERR "|" \
218  KEY_SPEC_SYE "|" \
219  KEY_LAMNLIN "|" \
220  KEY_LAMRMS "|" \
221  KEY_GAIN "|" \
222  KEY_DETRON "|" \
223  KEY_EFFRON "|" \
224  KEY_SNR "|" \
225  KEY_NCOMBINE "|" \
226  KEY_PROV "[0-9]+|" \
227  KEY_ASSON "[0-9]+|" \
228  KEY_ASSOC "[0-9]+|" \
229  KEY_ASSOM "[0-9]+|" \
230  KEY_VOCLASS "|" \
231  KEY_VOPUB "|" \
232  KEY_TITLE "|" \
233  KEY_OBJECT "|" \
234  KEY_APERTURE "|" \
235  KEY_TELAPSE "|" \
236  KEY_TMID "|" \
237  KEY_SPEC_VAL "|" \
238  KEY_SPEC_BW "|" \
239  KEY_TDMIN(1) "|" \
240  KEY_TDMAX(1) "|" \
241  KEY_TUTYP "[0-9]+|" \
242  KEY_TUCD "[0-9]+|" \
243  KEY_TCOMM "[0-9]+|" \
244  KEY_NELEM "|" \
245  KEY_EXTNAME "|" \
246  KEY_INHERIT ")$"
247 
248 /* A regular expression to select keywords from all explicit SDP spectrum
249  * keywords that should land up in the primary HDU. */
250 #define PRIMARY_HDU_KEYS_REGEXP \
251  "^(" KEY_RA "|" \
252  KEY_DEC "|" \
253  KEY_EXPTIME "|" \
254  KEY_TEXPTIME "|" \
255  KEY_TIMESYS "|" \
256  KEY_MJDOBS "|" \
257  KEY_MJDEND "|" \
258  KEY_PRODLVL "|" \
259  KEY_PROCSOFT "|" \
260  KEY_PRODCATG "|" \
261  KEY_ORIGIN "|" \
262  KEY_EXT_OBJ "|" \
263  KEY_DISPELEM "|" \
264  KEY_SPECSYS "|" \
265  KEY_PROG_ID "|" \
266  KEY_OBID "[0-9]+|" \
267  KEY_M_EPOCH "|" \
268  KEY_OBSTECH "|" \
269  KEY_FLUXCAL "|" \
270  KEY_CONTNORM "|" \
271  KEY_WAVELMIN "|" \
272  KEY_WAVELMAX "|" \
273  KEY_SPEC_BIN "|" \
274  KEY_TOT_FLUX "|" \
275  KEY_FLUXERR "|" \
276  KEY_REFERENC "|" \
277  KEY_SPEC_RES "|" \
278  KEY_SPEC_ERR "|" \
279  KEY_SPEC_SYE "|" \
280  KEY_LAMNLIN "|" \
281  KEY_LAMRMS "|" \
282  KEY_GAIN "|" \
283  KEY_DETRON "|" \
284  KEY_EFFRON "|" \
285  KEY_SNR "|" \
286  KEY_NCOMBINE "|" \
287  KEY_PROV "[0-9]+|" \
288  KEY_ASSON "[0-9]+|" \
289  KEY_ASSOC "[0-9]+|" \
290  KEY_ASSOM "[0-9]+|" \
291  KEY_OBJECT ")$"
292 
293 /* A regular expression to select keywords from all explicit SDP spectrum
294  * keywords that should land up in the extension HDU. */
295 #define EXTENSION_HDU_KEYS_REGEXP \
296  "^(" KEY_RA "|" \
297  KEY_DEC "|" \
298  KEY_VOCLASS "|" \
299  KEY_VOPUB "|" \
300  KEY_TITLE "|" \
301  KEY_OBJECT "|" \
302  KEY_APERTURE "|" \
303  KEY_TELAPSE "|" \
304  KEY_TMID "|" \
305  KEY_SPEC_VAL "|" \
306  KEY_SPEC_BW "|" \
307  KEY_TDMIN(1) "|" \
308  KEY_TDMAX(1) "|" \
309  KEY_TUTYP "[0-9]+|" \
310  KEY_TUCD "[0-9]+|" \
311  KEY_TCOMM "[0-9]+|" \
312  KEY_NELEM "|" \
313  KEY_EXTNAME "|" \
314  KEY_INHERIT ")$"
315 
316 
317 #define GET_SET_METHODS(param, keyname, type, rettype, defaultval, comment) \
318  rettype irplib_sdp_spectrum_get_##param(const irplib_sdp_spectrum *self) \
319  { \
320  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, defaultval); \
321  assert(self->proplist != NULL); \
322  if (cpl_propertylist_has(self->proplist, keyname)) { \
323  return cpl_propertylist_get_##type(self->proplist, keyname); \
324  } else { \
325  return defaultval; \
326  } \
327  } \
328  cpl_error_code irplib_sdp_spectrum_reset_##param(irplib_sdp_spectrum *self) \
329  { \
330  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); \
331  assert(self->proplist != NULL); \
332  (void) cpl_propertylist_erase(self->proplist, keyname); \
333  return CPL_ERROR_NONE; \
334  } \
335  cpl_error_code irplib_sdp_spectrum_set_##param(irplib_sdp_spectrum *self, \
336  rettype value) \
337  { \
338  cpl_error_code error; \
339  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); \
340  assert(self->proplist != NULL); \
341  if (cpl_propertylist_has(self->proplist, keyname)) { \
342  error = cpl_propertylist_set_##type(self->proplist, keyname, value); \
343  } else { \
344  error = cpl_propertylist_append_##type(self->proplist, keyname, value); \
345  if (! error) { \
346  error = cpl_propertylist_set_comment(self->proplist, keyname, comment);\
347  if (error) { \
348  /* Delete entry if we could not set the comment to maintain a */ \
349  /* consistent state. */ \
350  cpl_errorstate prestate = cpl_errorstate_get(); \
351  (void) cpl_propertylist_erase(self->proplist, keyname); \
352  cpl_errorstate_set(prestate); \
353  } \
354  } \
355  } \
356  return error; \
357  } \
358  cpl_error_code irplib_sdp_spectrum_copy_##param(irplib_sdp_spectrum *self, \
359  const cpl_propertylist *plist, const char *name) \
360  { \
361  /* Note: check for plist or name equal NULL is done in the CPL calls. */ \
362  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); \
363  assert(self->proplist != NULL); \
364  if (cpl_propertylist_has(plist, name)) { \
365  cpl_errorstate prestate = cpl_errorstate_get(); \
366  rettype value = cpl_propertylist_get_##type(plist, name); \
367  if (cpl_errorstate_is_equal(prestate)) { \
368  return irplib_sdp_spectrum_set_##param(self, value); \
369  } else { \
370  return cpl_error_set_message(cpl_func, cpl_error_get_code(), \
371  "Could not set '%s'. Likely the source '%s' keyword has a" \
372  " different format or type.", keyname, name); \
373  } \
374  } else { \
375  return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND, \
376  "Could not set '%s' since the '%s' keyword was not found.", \
377  keyname, name); \
378  } \
379  }
380 
381 #define GET_SET_ARRAY_METHODS(param, keyname, type, rettype, defaultval, \
382  comment) \
383  rettype irplib_sdp_spectrum_get_##param(const irplib_sdp_spectrum *self, \
384  cpl_size index) \
385  { \
386  char *name; \
387  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, defaultval); \
388  assert(self->proplist != NULL); \
389  name = cpl_sprintf("%s%"CPL_SIZE_FORMAT, keyname, index); \
390  rettype result = defaultval; \
391  if (cpl_propertylist_has(self->proplist, name)) { \
392  result = cpl_propertylist_get_##type(self->proplist, name); \
393  } \
394  cpl_free(name); \
395  return result; \
396  } \
397  cpl_error_code irplib_sdp_spectrum_reset_##param(irplib_sdp_spectrum *self, \
398  cpl_size index) \
399  { \
400  char *name; \
401  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); \
402  assert(self->proplist != NULL); \
403  name = cpl_sprintf("%s%"CPL_SIZE_FORMAT, keyname, index); \
404  (void) cpl_propertylist_erase(self->proplist, name); \
405  cpl_free(name); \
406  return CPL_ERROR_NONE; \
407  } \
408  cpl_error_code irplib_sdp_spectrum_set_##param(irplib_sdp_spectrum *self, \
409  cpl_size index, rettype value)\
410  { \
411  cpl_error_code error; \
412  char *name; \
413  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); \
414  assert(self->proplist != NULL); \
415  name = cpl_sprintf("%s%"CPL_SIZE_FORMAT, keyname, index); \
416  if (cpl_propertylist_has(self->proplist, name)) { \
417  error = cpl_propertylist_set_##type(self->proplist, name, value); \
418  } else { \
419  error = cpl_propertylist_append_##type(self->proplist, name, value); \
420  if (! error) { \
421  error = cpl_propertylist_set_comment(self->proplist, name, comment);\
422  if (error) { \
423  /* Delete entry if we could not set the comment to maintain a */ \
424  /* consistent state. */ \
425  cpl_errorstate prestate = cpl_errorstate_get(); \
426  (void) cpl_propertylist_erase(self->proplist, name); \
427  cpl_errorstate_set(prestate); \
428  } \
429  } \
430  } \
431  cpl_free(name); \
432  return error; \
433  } \
434  cpl_error_code irplib_sdp_spectrum_copy_##param(\
435  irplib_sdp_spectrum *self, cpl_size index, \
436  const cpl_propertylist *plist, const char *name) \
437  { \
438  /* Note: check for plist or name equal NULL is done in the CPL calls. */ \
439  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); \
440  assert(self->proplist != NULL); \
441  if (cpl_propertylist_has(plist, name)) { \
442  cpl_errorstate prestate = cpl_errorstate_get(); \
443  rettype value = cpl_propertylist_get_##type(plist, name); \
444  if (cpl_errorstate_is_equal(prestate)) { \
445  return irplib_sdp_spectrum_set_##param(self, index, value); \
446  } else { \
447  return cpl_error_set_message(cpl_func, cpl_error_get_code(), \
448  "Could not set '%s%"CPL_SIZE_FORMAT"'. Likely the source" \
449  " '%s' keyword has a different format or type.", \
450  keyname, index, name); \
451  } \
452  } else { \
453  return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND, \
454  "Could not set '%s%"CPL_SIZE_FORMAT"' since the '%s'" \
455  " keyword was not found.", keyname, index, name); \
456  } \
457  }
458 
459 
460 #define GET_SET_METHODS_TYPE_BOOL(param, keyname, comment) \
461  GET_SET_METHODS(param, keyname, bool, cpl_boolean, CPL_FALSE, comment)
462 
463 #define GET_SET_METHODS_TYPE_DOUBLE(param, keyname, comment) \
464  GET_SET_METHODS(param, keyname, double, double, NAN, comment)
465 
466 #define GET_SET_METHODS_TYPE_INT(param, keyname, comment) \
467  GET_SET_METHODS(param, keyname, int, int, -1, comment)
468 
469 #define GET_SET_METHODS_TYPE_STRING(param, keyname, comment) \
470  GET_SET_METHODS(param, keyname, string, const char *, NULL, comment)
471 
472 #define GET_SET_ARRAY_METHODS_TYPE_INT(param, keyname, comment) \
473  GET_SET_ARRAY_METHODS(param, keyname, int, int, -1, comment)
474 
475 #define GET_SET_ARRAY_METHODS_TYPE_STRING(param, keyname, comment) \
476  GET_SET_ARRAY_METHODS(param, keyname, string, const char *, NULL, comment)
477 
478 
479 #define IRPLIB_TYPE_NELEM CPL_TYPE_LONG_LONG | CPL_TYPE_UNSPECIFIED
480 
481 /*----------------------------------------------------------------------------*/
488 /*----------------------------------------------------------------------------*/
489 
492 /*-----------------------------------------------------------------------------
493  Type definition
494  -----------------------------------------------------------------------------*/
495 
500 struct _irplib_sdp_spectrum_ {
501  /* Indicates the number of data points of the spectrum. */
502  cpl_size nelem;
503 
504  /* Stores all the SDP keywords for the primary header and table extension. */
505  cpl_propertylist *proplist;
506 
507  /* The table for the spectrum data points. */
508  cpl_table *table;
509 };
510 
511 
516 typedef struct _irplib_keyword_record_ {
517  /* The name of the keyword. */
518  const char *name;
519 
520  /* The keyword's default comment. */
521  const char *comment;
522 
523  /* The keyword's type code. */
524  cpl_type type;
525 
526  /* Is the keyword an array keyword or not (e.g. PROVi). */
527  cpl_boolean is_array_key;
528 
529 } irplib_keyword_record;
530 
531 /*-----------------------------------------------------------------------------
532  Internal function prototypes
533  -----------------------------------------------------------------------------*/
534 
535 static cpl_boolean
536 _irplib_property_equal(const cpl_property *a, const cpl_property *b);
537 
538 static cpl_boolean
539 _irplib_array_equal(const cpl_array *a, const cpl_array *b, cpl_size n);
540 
541 static cpl_boolean
542 _irplib_table_column_equal(const cpl_table *a, const cpl_table *b,
543  const char *name, cpl_boolean only_intersect);
544 
545 static cpl_error_code
546 _irplib_sdp_spectrum_copy_column(irplib_sdp_spectrum *self, const char *to_name,
547  const cpl_table* table, const char *from_name);
548 
549 static cpl_size
550 _irplib_sdp_spectrum_count_keywords(const irplib_sdp_spectrum *self,
551  const char *regexp);
552 
553 static cpl_size
554 _irplib_sdp_spectrum_get_column_index(const irplib_sdp_spectrum *self,
555  const char *name);
556 
557 static const char *
558 _irplib_sdp_spectrum_get_column_keyword(const irplib_sdp_spectrum *self,
559  const char *name, const char *keyword);
560 
561 static cpl_error_code
562 _irplib_sdp_spectrum_set_column_keyword(irplib_sdp_spectrum *self,
563  const char *name,
564  const char *value,
565  const char *keyword,
566  const char *comment);
567 
568 static void
569 _irplib_sdp_spectrum_erase_column_keywords(irplib_sdp_spectrum *self,
570  const char *name);
571 
572 static char * _irplib_make_regexp(const cpl_propertylist *plist,
573  const char *extra);
574 
575 #ifndef NDEBUG
576 static cpl_boolean _irplib_keyword_table_is_sorted(
577  const irplib_keyword_record *table, size_t entries);
578 #endif
579 
580 static const irplib_keyword_record *
581 _irplib_sdp_spectrum_get_keyword_record(const char *name);
582 
583 /*-----------------------------------------------------------------------------
584  Function codes
585  -----------------------------------------------------------------------------*/
586 
587 /*----------------------------------------------------------------------------*/
591 /*----------------------------------------------------------------------------*/
592 irplib_sdp_spectrum * irplib_sdp_spectrum_new(void)
593 {
594  irplib_sdp_spectrum * obj = cpl_malloc(sizeof(irplib_sdp_spectrum));
595  obj->nelem = 0;
596  obj->proplist = cpl_propertylist_new();
597  obj->table = cpl_table_new(1);
598  return obj;
599 }
600 
601 /*----------------------------------------------------------------------------*/
605 /*----------------------------------------------------------------------------*/
607 irplib_sdp_spectrum_duplicate(const irplib_sdp_spectrum *other)
608 {
609  irplib_sdp_spectrum * obj;
610 
611  cpl_ensure(other != NULL, CPL_ERROR_NULL_INPUT, NULL);
612 
613  assert(other->proplist != NULL);
614  assert(other->table != NULL);
615 
616  obj = cpl_malloc(sizeof(irplib_sdp_spectrum));
617  obj->nelem = other->nelem;
618  obj->proplist = cpl_propertylist_duplicate(other->proplist);
619  obj->table = cpl_table_duplicate(other->table);
620  return obj;
621 }
622 
623 /*----------------------------------------------------------------------------*/
627 /*----------------------------------------------------------------------------*/
628 void irplib_sdp_spectrum_delete(irplib_sdp_spectrum *self)
629 {
630  if (self != NULL) {
631  assert(self->proplist != NULL);
632  assert(self->table != NULL);
633  cpl_propertylist_delete(self->proplist);
634  cpl_table_delete(self->table);
635  cpl_free(self);
636  }
637 }
638 
639 /*----------------------------------------------------------------------------*/
644 /*----------------------------------------------------------------------------*/
645 static cpl_boolean
646 _irplib_property_equal(const cpl_property *a, const cpl_property *b)
647 {
648  int value_not_equal;
649  cpl_type type;
650  const char *sa, *sb;
651 
652  assert(a != NULL);
653  assert(b != NULL);
654 
655  /* Check the types are the same. */
656  type = cpl_property_get_type(a);
657  if (cpl_property_get_type(b) != type) return CPL_FALSE;
658 
659  /* Check that the values are the same. */
660  switch (type) {
661  case CPL_TYPE_CHAR:
662  value_not_equal = cpl_property_get_char(a) != cpl_property_get_char(b);
663  break;
664  case CPL_TYPE_BOOL:
665  value_not_equal = cpl_property_get_bool(a) != cpl_property_get_bool(b);
666  break;
667  case CPL_TYPE_INT:
668  value_not_equal = cpl_property_get_int(a) != cpl_property_get_int(b);
669  break;
670  case CPL_TYPE_LONG:
671  value_not_equal = cpl_property_get_long(a) != cpl_property_get_long(b);
672  break;
673  case CPL_TYPE_LONG_LONG:
674  value_not_equal =
675  cpl_property_get_long_long(a) != cpl_property_get_long_long(b);
676  break;
677  case CPL_TYPE_FLOAT:
678  value_not_equal = cpl_property_get_float(a) != cpl_property_get_float(b);
679  break;
680  case CPL_TYPE_DOUBLE:
681  value_not_equal = cpl_property_get_double(a) != cpl_property_get_double(b);
682  break;
683  case CPL_TYPE_STRING:
684  sa = cpl_property_get_string(a);
685  sb = cpl_property_get_string(b);
686  if (sa == NULL && sb == NULL) {
687  value_not_equal = 0;
688  } else if (sa != NULL && sb != NULL) {
689  value_not_equal = strcmp(sa, sb) != 0;
690  } else {
691  return CPL_FALSE;
692  }
693  break;
694 #ifdef _Complex_I
695  case CPL_TYPE_FLOAT_COMPLEX:
696  value_not_equal =
697  cpl_property_get_float_complex(a) != cpl_property_get_float_complex(b);
698  break;
699  case CPL_TYPE_DOUBLE_COMPLEX:
700  value_not_equal =
701  cpl_property_get_double_complex(a) != cpl_property_get_double_complex(b);
702  break;
703 #endif
704  default:
705  cpl_error_set_message(cpl_func, CPL_ERROR_INVALID_TYPE,
706  "Unsupported data type found in property '%s'.",
707  cpl_property_get_name(a));
708  return CPL_FALSE;
709  }
710  if (value_not_equal) return CPL_FALSE;
711 
712  /* If we got here then the type and value must be equal so return true. */
713  return CPL_TRUE;
714 }
715 
716 
717 /*----------------------------------------------------------------------------*/
728 /*----------------------------------------------------------------------------*/
729 static cpl_boolean
730 _irplib_array_equal(const cpl_array *a, const cpl_array *b, cpl_size n)
731 {
732  cpl_type type;
733 
734  assert(a != NULL);
735  assert(b != NULL);
736  assert(n <= cpl_array_get_size(a));
737  assert(n <= cpl_array_get_size(b));
738 
739  type = cpl_array_get_type(a);
740  if (type != cpl_array_get_type(b)) return CPL_FALSE;
741 
742  if (type == CPL_TYPE_STRING) {
743  /* Handle strings: */
744  cpl_size i;
745  const char **stra = cpl_array_get_data_string_const(a);
746  const char **strb = cpl_array_get_data_string_const(b);
747  cpl_error_ensure(stra != NULL && strb != NULL, cpl_error_get_code(),
748  return CPL_FALSE, "Failed to get %s data for array.",
749  cpl_type_get_name(type));
750  for (i = 0; i < n; ++i) {
751  if (stra[i] == NULL && strb[i] == NULL) continue;
752  if (stra[i] == NULL || strb[i] == NULL) return CPL_FALSE;
753  if (strcmp(stra[i], strb[i]) != 0) return CPL_FALSE;
754  }
755 
756  } else {
757  /* Handle fundamental types: */
758  cpl_size size, i;
759  const void *va, *vb;
760 
761  switch (type) {
762  case CPL_TYPE_INT:
763  size = sizeof(int);
764  va = cpl_array_get_data_int_const(a);
765  vb = cpl_array_get_data_int_const(b);
766  break;
767  case CPL_TYPE_LONG_LONG:
768  size = sizeof(long long);
769  va = cpl_array_get_data_long_long_const(a);
770  vb = cpl_array_get_data_long_long_const(b);
771  break;
772  case CPL_TYPE_FLOAT:
773  size = sizeof(float);
774  va = cpl_array_get_data_float_const(a);
775  vb = cpl_array_get_data_float_const(b);
776  break;
777  case CPL_TYPE_DOUBLE:
778  size = sizeof(double);
779  va = cpl_array_get_data_double_const(a);
780  vb = cpl_array_get_data_double_const(b);
781  break;
782 #ifdef _Complex_I
783  case CPL_TYPE_FLOAT_COMPLEX:
784  size = sizeof(_Complex float);
785  va = cpl_array_get_data_float_complex_const(a);
786  vb = cpl_array_get_data_float_complex_const(b);
787  break;
788  case CPL_TYPE_DOUBLE_COMPLEX:
789  size = sizeof(_Complex double);
790  va = cpl_array_get_data_double_complex_const(a);
791  vb = cpl_array_get_data_double_complex_const(b);
792  break;
793 #endif
794  default:
795  cpl_error_set_message(cpl_func, CPL_ERROR_INVALID_TYPE,
796  "Unsupported data type.");
797  return CPL_FALSE;
798  }
799  cpl_error_ensure(va != NULL && vb != NULL, cpl_error_get_code(),
800  return CPL_FALSE, "Failed to get %s data for array.",
801  cpl_type_get_name(type));
802  for (i = 0; i < n; ++i) {
803  int valid_a = cpl_array_is_valid(a, i);
804  int valid_b = cpl_array_is_valid(b, i);
805  if (! valid_a && ! valid_b) continue;
806  if (! valid_a || ! valid_b) return CPL_FALSE;
807  const void *vai = (const char *)va + (size * i);
808  const void *vbi = (const char *)vb + (size * i);
809  if (memcmp(vai, vbi, size) != 0) return CPL_FALSE;
810  }
811  }
812 
813  /* If we get here then the first n elements of the arrays are equal. */
814  return TRUE;
815 }
816 
817 /*----------------------------------------------------------------------------*/
830 /*----------------------------------------------------------------------------*/
831 static cpl_boolean
832 _irplib_table_column_equal(const cpl_table *a, const cpl_table *b,
833  const char *name, cpl_boolean only_intersect)
834 {
835  cpl_type type;
836  cpl_size nrows, na, nb, i;
837  const char *sa, *sb;
838 
839  assert(a != NULL);
840  assert(b != NULL);
841 
842  nrows = cpl_table_get_nrow(a);
843  if (only_intersect) {
844  cpl_size nrows2 = cpl_table_get_nrow(b);
845  if (nrows2 < nrows) nrows = nrows2;
846  } else {
847  if (cpl_table_get_nrow(b) != nrows) return CPL_FALSE;
848  }
849 
850  /* Column types must be the same. */
851  type = cpl_table_get_column_type(a, name);
852  if (cpl_table_get_column_type(b, name) != type) return CPL_FALSE;
853 
854  /* Column dimensions must be the same. */
855  na = cpl_table_get_column_dimensions(a, name);
856  nb = cpl_table_get_column_dimensions(b, name);
857  if (na != nb) return CPL_FALSE;
858 
859  /* Check that the column unit is the same. */
860  sa = cpl_table_get_column_unit(a, name);
861  sb = cpl_table_get_column_unit(b, name);
862  cpl_error_ensure(sa != NULL && sb != NULL, cpl_error_get_code(),
863  return CPL_FALSE,
864  "Failed to get unit strings for column '%s'.", name);
865  if (strcmp(sa, sb) != 0) return CPL_FALSE;
866 
867  /* Check that the values are the same. For arrays we check that the parts of
868  * the arrays that overlap are at least the same. */
869  if (type & CPL_TYPE_POINTER) {
870  cpl_errorstate prestate;
871  /* Handle array cells: */
872  const cpl_array **va = cpl_table_get_data_array_const(a, name);
873  const cpl_array **vb = cpl_table_get_data_array_const(b, name);
874  cpl_error_ensure(va != NULL && vb != NULL,
875  cpl_error_get_code(), return CPL_FALSE,
876  "Failed to get %s data for column '%s'.",
877  cpl_type_get_name(type), name);
878  if (only_intersect) {
879  for (i = 0; i < nrows; ++i) {
880  /* If both arrays are NULL then they are equal,
881  * but not if only one is NULL. */
882  if (va[i] == NULL && vb[i] == NULL) continue;
883  if (va[i] == NULL || vb[i] == NULL) return CPL_FALSE;
884  prestate = cpl_errorstate_get();
885  cpl_size n1 = cpl_array_get_size(va[i]);
886  cpl_size n2 = cpl_array_get_size(vb[i]);
887  cpl_size n = n1 < n2 ? n1 : n2;
888  if (! _irplib_array_equal(va[i], vb[i], n)) return CPL_FALSE;
889  cpl_error_ensure(cpl_errorstate_is_equal(prestate),
890  cpl_error_get_code(), return CPL_FALSE,
891  "Failed when trying to match %s data for column '%s'.",
892  cpl_type_get_name(type), name);
893  }
894  } else {
895  for (i = 0; i < nrows; ++i) {
896  /* If both arrays are NULL then they are equal,
897  * but not if only one is NULL. */
898  if (va[i] == NULL && vb[i] == NULL) continue;
899  if (va[i] == NULL || vb[i] == NULL) return CPL_FALSE;
900  prestate = cpl_errorstate_get();
901  cpl_size n = cpl_array_get_size(va[i]);
902  if (n != cpl_array_get_size(vb[i])) return CPL_FALSE;
903  if (! _irplib_array_equal(va[i], vb[i], n)) return CPL_FALSE;
904  cpl_error_ensure(cpl_errorstate_is_equal(prestate),
905  cpl_error_get_code(), return CPL_FALSE,
906  "Failed when trying to match %s data for column '%s'.",
907  cpl_type_get_name(type), name);
908  }
909  }
910 
911  } else if (type == CPL_TYPE_STRING) {
912  /* Handle strings: */
913  const char **va = cpl_table_get_data_string_const(a, name);
914  const char **vb = cpl_table_get_data_string_const(b, name);
915  cpl_error_ensure(va != NULL && vb != NULL,
916  cpl_error_get_code(), return CPL_FALSE,
917  "Failed to get %s data for column '%s'.",
918  cpl_type_get_name(type), name);
919  if (only_intersect) {
920  for (i = 0; i < nrows; ++i) {
921  if (va[i] == NULL && vb[i] == NULL) continue;
922  if (va[i] == NULL || vb[i] == NULL) return CPL_FALSE;
923  size_t n1 = strlen(va[i]);
924  size_t n2 = strlen(vb[i]);
925  size_t n = n1 < n2 ? n1 : n2;
926  if (strncmp(va[i], vb[i], (cpl_size)n) != 0) return CPL_FALSE;
927  }
928  } else {
929  for (i = 0; i < nrows; ++i) {
930  if (va[i] == NULL && vb[i] == NULL) continue;
931  if (va[i] == NULL || vb[i] == NULL) return CPL_FALSE;
932  if (strcmp(va[i], vb[i]) != 0) return CPL_FALSE;
933  }
934  }
935 
936  } else {
937  /* Handle fundamental types and strings: */
938  cpl_size size;
939  const void *va, *vb;
940 
941  switch (type) {
942  case CPL_TYPE_INT:
943  size = sizeof(int);
944  va = cpl_table_get_data_int_const(a, name);
945  vb = cpl_table_get_data_int_const(b, name);
946  break;
947  case CPL_TYPE_LONG_LONG:
948  size = sizeof(long long);
949  va = cpl_table_get_data_long_long_const(a, name);
950  vb = cpl_table_get_data_long_long_const(b, name);
951  break;
952  case CPL_TYPE_FLOAT:
953  size = sizeof(float);
954  va = cpl_table_get_data_float_const(a, name);
955  vb = cpl_table_get_data_float_const(b, name);
956  break;
957  case CPL_TYPE_DOUBLE:
958  size = sizeof(double);
959  va = cpl_table_get_data_double_const(a, name);
960  vb = cpl_table_get_data_double_const(b, name);
961  break;
962 #ifdef _Complex_I
963  case CPL_TYPE_FLOAT_COMPLEX:
964  size = sizeof(_Complex float);
965  va = cpl_table_get_data_float_complex_const(a, name);
966  vb = cpl_table_get_data_float_complex_const(b, name);
967  break;
968  case CPL_TYPE_DOUBLE_COMPLEX:
969  size = sizeof(_Complex double);
970  va = cpl_table_get_data_double_complex_const(a, name);
971  vb = cpl_table_get_data_double_complex_const(b, name);
972  break;
973 #endif
974  default:
975  cpl_error_set_message(cpl_func, CPL_ERROR_INVALID_TYPE,
976  "Unsupported data type found in column '%s'.", name);
977  return CPL_FALSE;
978  }
979  cpl_error_ensure(va != NULL && vb != NULL,
980  cpl_error_get_code(), return CPL_FALSE,
981  "Failed to get %s data for column '%s'.",
982  cpl_type_get_name(type), name);
983  for (i = 0; i < nrows; ++i) {
984  int valid_a = cpl_table_is_valid(a, name, i);
985  int valid_b = cpl_table_is_valid(b, name, i);
986  if (! valid_a && ! valid_b) continue;
987  if (! valid_a || ! valid_b) return CPL_FALSE;
988  const void *vai = (const char *)va + (size * i);
989  const void *vbi = (const char *)vb + (size * i);
990  if (memcmp(vai, vbi, size) != 0) return CPL_FALSE;
991  }
992  }
993 
994  /* If we got here then the columns must be equal so return true. */
995  return CPL_TRUE;
996 }
997 
998 /*----------------------------------------------------------------------------*/
1014 /*----------------------------------------------------------------------------*/
1015 cpl_boolean irplib_sdp_spectrum_equal(const irplib_sdp_spectrum *a,
1016  const irplib_sdp_spectrum *b,
1017  cpl_boolean only_intersect)
1018 {
1019  cpl_errorstate prestate;
1020  cpl_size na, i;
1021  cpl_boolean no_match = CPL_FALSE;
1022  cpl_array *names;
1023  const char *name;
1024  const cpl_property *pa, *pb;
1025 
1026  cpl_ensure(a != NULL && b != NULL, CPL_ERROR_NULL_INPUT, CPL_FALSE);
1027 
1028  assert(a->proplist != NULL);
1029  assert(a->table != NULL);
1030  assert(b->proplist != NULL);
1031  assert(b->table != NULL);
1032 
1033  na = cpl_propertylist_get_size(a->proplist);
1034 
1035  if (only_intersect) {
1036  /* Check that the values are the same if the keywords are in both property
1037  * lists. (Ignore comments) */
1038  for (i = 0; i < na; ++i) {
1039  pa = cpl_propertylist_get_const(a->proplist, i);
1040  cpl_error_ensure(pa != NULL, cpl_error_get_code(), return CPL_FALSE,
1041  "Failed to get property structure %"CPL_SIZE_FORMAT".", i);
1042  name = cpl_property_get_name(pa);
1043  cpl_error_ensure(name != NULL, cpl_error_get_code(), return CPL_FALSE,
1044  "Failed to get the name for property %"CPL_SIZE_FORMAT".", i);
1045  pb = cpl_propertylist_get_property_const(b->proplist, name);
1046  if (pb != NULL) {
1047  prestate = cpl_errorstate_get();
1048  if (! _irplib_property_equal(pa, pb)) return CPL_FALSE;
1049  if (! cpl_errorstate_is_equal(prestate)) return CPL_FALSE;
1050  }
1051  }
1052 
1053  /* Check that the columns with the same names in both tables are identical
1054  * for the parts of the data arrays that overlap. */
1055  prestate = cpl_errorstate_get();
1056  na = cpl_table_get_ncol(a->table);
1057  names = cpl_table_get_column_names(a->table);
1058  for (i = 0; i < na; ++i) {
1059  name = cpl_array_get_string(names, i);
1060  cpl_error_ensure(name != NULL, cpl_error_get_code(), break,
1061  "Failed to get the name for column %"CPL_SIZE_FORMAT".", i);
1062  if (cpl_table_has_column(b->table, name)) {
1063  if (! _irplib_table_column_equal(a->table, b->table, name, CPL_TRUE)) {
1064  no_match = CPL_TRUE;
1065  break;
1066  }
1067  }
1068  }
1069  cpl_array_delete(names);
1070  /* Check that no errors occurred and all columns were processed. */
1071  if (no_match || ! cpl_errorstate_is_equal(prestate)) return CPL_FALSE;
1072 
1073  } else { /* not only_intersect */
1074  cpl_size nb;
1075  if (a->nelem != b->nelem) return CPL_FALSE;
1076 
1077  /* Check that the property lists are identical. (Ignore comments) */
1078  nb = cpl_propertylist_get_size(b->proplist);
1079  if (na != nb) return CPL_FALSE;
1080  for (i = 0; i < na; ++i) {
1081  pa = cpl_propertylist_get_const(a->proplist, i);
1082  cpl_error_ensure(pa != NULL, cpl_error_get_code(), return CPL_FALSE,
1083  "Failed to get property structure %"CPL_SIZE_FORMAT".", i);
1084  name = cpl_property_get_name(pa);
1085  cpl_error_ensure(name != NULL, cpl_error_get_code(), return CPL_FALSE,
1086  "Failed to get the name for property %"CPL_SIZE_FORMAT".", i);
1087  pb = cpl_propertylist_get_property_const(b->proplist, name);
1088  if (pb == NULL) return CPL_FALSE;
1089  prestate = cpl_errorstate_get();
1090  if (! _irplib_property_equal(pa, pb)) return CPL_FALSE;
1091  if (! cpl_errorstate_is_equal(prestate)) return CPL_FALSE;
1092  }
1093 
1094  /* Check that the tables are identical. */
1095  prestate = cpl_errorstate_get();
1096  na = cpl_table_get_ncol(a->table);
1097  nb = cpl_table_get_ncol(b->table);
1098  if (na != nb) return CPL_FALSE;
1099  names = cpl_table_get_column_names(a->table);
1100  for (i = 0; i < na; ++i) {
1101  name = cpl_array_get_string(names, i);
1102  cpl_error_ensure(name != NULL, cpl_error_get_code(), break,
1103  "Failed to get the name for column %"CPL_SIZE_FORMAT".", i);
1104  if (! cpl_table_has_column(b->table, name)
1105  || ! _irplib_table_column_equal(a->table, b->table, name, CPL_FALSE))
1106  {
1107  no_match = CPL_TRUE;
1108  break;
1109  }
1110  }
1111  cpl_array_delete(names);
1112  /* Check that no errors occurred and all columns were processed. */
1113  if (no_match || ! cpl_errorstate_is_equal(prestate)) return CPL_FALSE;
1114  }
1115 
1116  /* If we got to this point then all checks passed, so return true (a == b). */
1117  return CPL_TRUE;
1118 }
1119 
1120 /*----------------------------------------------------------------------------*/
1125 /*----------------------------------------------------------------------------*/
1126 static cpl_size
1127 _irplib_sdp_spectrum_count_keywords(const irplib_sdp_spectrum *self,
1128  const char *regexp)
1129 {
1130  cpl_error_code error;
1131  cpl_size result = 0;
1132  cpl_propertylist *list = cpl_propertylist_new();
1133 
1134  assert(self != NULL);
1135  assert(self->proplist != NULL);
1136 
1137  error = cpl_propertylist_copy_property_regexp(list, self->proplist, regexp,
1138  CPL_FALSE);
1139  if (! error) {
1140  result = cpl_propertylist_get_size(list);
1141  }
1142  cpl_propertylist_delete(list);
1143  return result;
1144 }
1145 
1146 
1147 cpl_size irplib_sdp_spectrum_count_obid(const irplib_sdp_spectrum *self)
1148 {
1149  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1150  return _irplib_sdp_spectrum_count_keywords(self, "^OBID[0-9]+$");
1151 }
1152 
1153 cpl_size irplib_sdp_spectrum_count_prov(const irplib_sdp_spectrum *self)
1154 {
1155  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1156  return _irplib_sdp_spectrum_count_keywords(self, "^PROV[0-9]+$");
1157 }
1158 
1159 cpl_size irplib_sdp_spectrum_count_asson(const irplib_sdp_spectrum *self)
1160 {
1161  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1162  return _irplib_sdp_spectrum_count_keywords(self, "^ASSON[0-9]+$");
1163 }
1164 
1165 cpl_size irplib_sdp_spectrum_count_assoc(const irplib_sdp_spectrum *self)
1166 {
1167  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1168  return _irplib_sdp_spectrum_count_keywords(self, "^ASSOC[0-9]+$");
1169 }
1170 
1171 cpl_size irplib_sdp_spectrum_count_assom(const irplib_sdp_spectrum *self)
1172 {
1173  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1174  return _irplib_sdp_spectrum_count_keywords(self, "^ASSOM[0-9]+$");
1175 }
1176 
1177 
1178 #ifndef NDEBUG
1179 
1184 static cpl_boolean _irplib_keyword_table_is_sorted(
1185  const irplib_keyword_record *table, size_t entries)
1186 {
1187  size_t i;
1188  if (entries < 2) return CPL_TRUE;
1189  for (i = 0; i < entries-1; ++i) {
1190  if (strcmp(table[i].name, table[i+1].name) >= 0) {
1191  return CPL_FALSE;
1192  }
1193  }
1194  return CPL_TRUE;
1195 }
1196 
1197 #endif /* NDEBUG */
1198 
1199 
1200 static const irplib_keyword_record *
1201 _irplib_sdp_spectrum_get_keyword_record(const char *name)
1202 {
1203  /* The following table should contain all valid SDP spectrum keywords being
1204  * handled. NOTE: this table must be kept sorted since we perform a binary
1205  * search on the first column (i.e. the keyword name). */
1206  static const irplib_keyword_record keyword_table[] = {
1207  /* Name Comment Type Is an array key */
1208  {KEY_APERTURE, KEY_APERTURE_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1209  {KEY_ASSOC, KEY_ASSOC_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1210  {KEY_ASSOM, KEY_ASSOM_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1211  {KEY_ASSON, KEY_ASSON_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1212  {KEY_CONTNORM, KEY_CONTNORM_COMMENT, CPL_TYPE_BOOL, CPL_FALSE},
1213  {KEY_DEC, KEY_DEC_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1214  {KEY_DETRON, KEY_DETRON_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1215  {KEY_DISPELEM, KEY_DISPELEM_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1216  {KEY_EFFRON, KEY_EFFRON_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1217  {KEY_EXPTIME, KEY_EXPTIME_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1218  {KEY_EXTNAME, KEY_EXTNAME_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1219  {KEY_EXT_OBJ, KEY_EXT_OBJ_COMMENT, CPL_TYPE_BOOL, CPL_FALSE},
1220  {KEY_FLUXCAL, KEY_FLUXCAL_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1221  {KEY_FLUXERR, KEY_FLUXERR_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1222  {KEY_GAIN, KEY_GAIN_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1223  {KEY_INHERIT, KEY_INHERIT_COMMENT, CPL_TYPE_BOOL, CPL_FALSE},
1224  {KEY_LAMNLIN, KEY_LAMNLIN_COMMENT, CPL_TYPE_INT, CPL_FALSE},
1225  {KEY_LAMRMS, KEY_LAMRMS_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1226  {KEY_MJDEND, KEY_MJDEND_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1227  {KEY_MJDOBS, KEY_MJDOBS_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1228  {KEY_M_EPOCH, KEY_M_EPOCH_COMMENT, CPL_TYPE_BOOL, CPL_FALSE},
1229  {KEY_NCOMBINE, KEY_NCOMBINE_COMMENT, CPL_TYPE_INT, CPL_FALSE},
1230  {KEY_NELEM, KEY_NELEM_COMMENT, IRPLIB_TYPE_NELEM, CPL_FALSE},
1231  {KEY_OBID, KEY_OBID_COMMENT, CPL_TYPE_INT, CPL_TRUE},
1232  {KEY_OBJECT, KEY_OBJECT_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1233  {KEY_OBSTECH, KEY_OBSTECH_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1234  {KEY_ORIGIN, KEY_ORIGIN_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1235  {KEY_PROCSOFT, KEY_PROCSOFT_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1236  {KEY_PRODCATG, KEY_PRODCATG_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1237  {KEY_PRODLVL, KEY_PRODLVL_COMMENT, CPL_TYPE_INT, CPL_FALSE},
1238  {KEY_PROG_ID, KEY_PROG_ID_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1239  {KEY_PROV, KEY_PROV_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1240  {KEY_RA, KEY_RA_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1241  {KEY_REFERENC, KEY_REFERENC_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1242  {KEY_SNR, KEY_SNR_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1243  {KEY_SPECSYS, KEY_SPECSYS_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1244  {KEY_SPEC_BIN, KEY_SPEC_BIN_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1245  {KEY_SPEC_BW, KEY_SPEC_BW_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1246  {KEY_SPEC_ERR, KEY_SPEC_ERR_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1247  {KEY_SPEC_RES, KEY_SPEC_RES_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1248  {KEY_SPEC_SYE, KEY_SPEC_SYE_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1249  {KEY_SPEC_VAL, KEY_SPEC_VAL_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1250  {KEY_TCOMM, KEY_TCOMM_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1251  {KEY_TDMAX(1), KEY_TDMAX1_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1252  {KEY_TDMIN(1), KEY_TDMIN1_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1253  {KEY_TELAPSE, KEY_TELAPSE_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1254  {KEY_TEXPTIME, KEY_TEXPTIME_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1255  {KEY_TIMESYS, KEY_TIMESYS_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1256  {KEY_TITLE, KEY_TITLE_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1257  {KEY_TMID, KEY_TMID_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1258  {KEY_TOT_FLUX, KEY_TOT_FLUX_COMMENT, CPL_TYPE_BOOL, CPL_FALSE},
1259  {KEY_TUCD, KEY_TUCD_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1260  {KEY_TUTYP, KEY_TUTYP_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1261  {KEY_VOCLASS, KEY_VOCLASS_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1262  {KEY_VOPUB, KEY_VOPUB_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1263  {KEY_WAVELMAX, KEY_WAVELMAX_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1264  {KEY_WAVELMIN, KEY_WAVELMIN_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE}
1265  };
1266 
1267  static const size_t tablesize =
1268  sizeof(keyword_table) / sizeof(irplib_keyword_record);
1269  size_t low = 0; /* Low end of search region. */
1270  size_t high = tablesize-1; /* High end of search region. */
1271  const irplib_keyword_record *record = NULL;
1272 
1273  assert(_irplib_keyword_table_is_sorted(keyword_table, tablesize));
1274  assert(name != NULL);
1275 
1276  /* Binary search for the keyword record who's name forms the prefix of the
1277  * 'name' string passed to this function or is equal to that string. We cannot
1278  * just check if they equal since the OBIDi, PROVi, ASSONi, ASSOCi, ASSOMi,
1279  * TUTYPi and TUCDi keywords all have a number suffix that needs to be dealt
1280  * with. */
1281  do {
1282  size_t mid = (low + high) >> 1; /* Find mid point between low and high. */
1283  size_t keylen = strlen(keyword_table[mid].name);
1284  int result = strncmp(name, keyword_table[mid].name, keylen);
1285  if (result == 0) {
1286  record = &keyword_table[mid];
1287  break;
1288  } else if (result < 0) {
1289  if (mid >= 1) {
1290  high = mid - 1;
1291  } else {
1292  return NULL;
1293  }
1294  } else {
1295  low = mid + 1;
1296  if (low > high) return NULL;
1297  }
1298  } while (1);
1299 
1300  assert(record != NULL);
1301 
1302  if (strlen(record->name) != strlen(name)) {
1303  if (! record->is_array_key) return NULL;
1304  /* Have to check if the keyword format is correct. Should only have digits
1305  * following the name prefix. */
1306  const char *c = name + strlen(record->name);
1307  while (*c != '\0') {
1308  if (! isdigit(*c)) return NULL;
1309  ++c;
1310  }
1311  }
1312 
1313  return record;
1314 }
1315 
1316 
1317 cpl_error_code irplib_sdp_spectrum_copy_keyword(irplib_sdp_spectrum *self,
1318  const cpl_propertylist *plist,
1319  const char *name)
1320 {
1321  const irplib_keyword_record *key;
1322  cpl_errorstate prestate = cpl_errorstate_get();
1323  cpl_boolean spectrum_has_keyword;
1324 
1325  cpl_ensure_code(self != NULL && plist != NULL && name != NULL,
1326  CPL_ERROR_NULL_INPUT);
1327 
1328  assert(self->proplist != NULL);
1329 
1330  if (! cpl_propertylist_has(plist, name)) {
1331  return cpl_error_set_message(cpl_func, cpl_error_get_code(),
1332  "Could not set '%s' since the keyword was not found in the"
1333  " source list.", name);
1334  }
1335 
1336  key = _irplib_sdp_spectrum_get_keyword_record(name);
1337  if (key == NULL) {
1338  return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
1339  "The keyword name '%s' is not valid for an SPD spectrum.",
1340  name);
1341  }
1342 
1343  spectrum_has_keyword = cpl_propertylist_has(self->proplist, name);
1344 
1345  switch ((int) key->type) {
1346  case CPL_TYPE_BOOL:
1347  {
1348  /* Note: we update with the following functions rather than using the
1349  * cpl_propertylist_copy_property function since this way we get basic
1350  * typecasting functionality. e.g. floats get converted to doubles. */
1351  cpl_boolean value = cpl_propertylist_get_bool(plist, name);
1352  cpl_propertylist_update_bool(self->proplist, name, value);
1353  }
1354  break;
1355  case CPL_TYPE_INT:
1356  {
1357  int value = cpl_propertylist_get_int(plist, name);
1358  cpl_propertylist_update_int(self->proplist, name, value);
1359  }
1360  break;
1361  case CPL_TYPE_DOUBLE:
1362  {
1363  double value = cpl_propertylist_get_double(plist, name);
1364  cpl_propertylist_update_double(self->proplist, name, value);
1365  }
1366  break;
1367  case CPL_TYPE_STRING:
1368  {
1369  const char *value = cpl_propertylist_get_string(plist, name);
1370  cpl_propertylist_update_string(self->proplist, name, value);
1371  }
1372  break;
1373  case IRPLIB_TYPE_NELEM:
1374  {
1375  /* Special case where we update the nelem field. */
1376  spectrum_has_keyword = CPL_TRUE; /* Skip trying to set comment. */
1377  cpl_size value = (cpl_size) cpl_propertylist_get_long_long(plist, name);
1378  if (cpl_errorstate_is_equal(prestate)) {
1379  irplib_sdp_spectrum_set_nelem(self, value);
1380  }
1381  }
1382  break;
1383  default:
1384  return cpl_error_set_message(cpl_func, CPL_ERROR_INVALID_TYPE,
1385  "Cannot handle type '%s'.", cpl_type_get_name(key->type));
1386  }
1387 
1388  if (! spectrum_has_keyword) {
1389  cpl_propertylist_set_comment(self->proplist, name, key->comment);
1390  }
1391 
1392  if (! cpl_errorstate_is_equal(prestate)) {
1393  if (! spectrum_has_keyword) {
1394  /* Make sure the keyword is removed if we have an error and it was not
1395  * there to begin with. */
1396  prestate = cpl_errorstate_get();
1397  (void) cpl_propertylist_erase(self->proplist, name);
1398  cpl_errorstate_set(prestate);
1399  }
1400  return cpl_error_set_message(cpl_func, cpl_error_get_code(),
1401  "Could not set '%s'. Likely the keyword from the source list"
1402  " has a different format or type.", name);
1403  }
1404 
1405  return CPL_ERROR_NONE;
1406 }
1407 
1408 
1409 cpl_error_code irplib_sdp_spectrum_copy_property(irplib_sdp_spectrum *self,
1410  const cpl_property *prop)
1411 {
1412  const char *name;
1413  const irplib_keyword_record *key;
1414  cpl_errorstate prestate = cpl_errorstate_get();
1415  cpl_boolean spectrum_has_keyword;
1416 
1417  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1418 
1419  assert(self->proplist != NULL);
1420 
1421  name = cpl_property_get_name(prop);
1422  if (name == NULL) return cpl_error_get_code();
1423 
1424  key = _irplib_sdp_spectrum_get_keyword_record(name);
1425  if (key == NULL) {
1426  return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
1427  "The keyword name '%s' is not valid for an SPD spectrum.",
1428  name);
1429  }
1430 
1431  spectrum_has_keyword = cpl_propertylist_has(self->proplist, name);
1432 
1433  switch ((int) key->type) {
1434  case CPL_TYPE_BOOL:
1435  {
1436  cpl_boolean value = cpl_property_get_bool(prop);
1437  cpl_propertylist_update_bool(self->proplist, name, value);
1438  }
1439  break;
1440  case CPL_TYPE_INT:
1441  {
1442  int value = cpl_property_get_int(prop);
1443  cpl_propertylist_update_int(self->proplist, name, value);
1444  }
1445  break;
1446  case CPL_TYPE_DOUBLE:
1447  {
1448  double value = cpl_property_get_double(prop);
1449  cpl_propertylist_update_double(self->proplist, name, value);
1450  }
1451  break;
1452  case CPL_TYPE_STRING:
1453  {
1454  const char *value = cpl_property_get_string(prop);
1455  cpl_propertylist_update_string(self->proplist, name, value);
1456  }
1457  break;
1458  case IRPLIB_TYPE_NELEM:
1459  {
1460  /* Special case where we update the nelem field. */
1461  spectrum_has_keyword = CPL_TRUE; /* Skip trying to set comment. */
1462  cpl_size value = (cpl_size) cpl_property_get_long_long(prop);
1463  if (cpl_errorstate_is_equal(prestate)) {
1464  irplib_sdp_spectrum_set_nelem(self, value);
1465  }
1466  }
1467  break;
1468  default:
1469  return cpl_error_set_message(cpl_func, CPL_ERROR_INVALID_TYPE,
1470  "Cannot handle type '%s'.", cpl_type_get_name(key->type));
1471  }
1472 
1473  if (! spectrum_has_keyword) {
1474  cpl_propertylist_set_comment(self->proplist, name, key->comment);
1475  }
1476 
1477  if (! cpl_errorstate_is_equal(prestate)) {
1478  if (! spectrum_has_keyword) {
1479  /* Make sure the keyword is removed if we have an error and it was not
1480  * there to begin with. */
1481  prestate = cpl_errorstate_get();
1482  (void) cpl_propertylist_erase(self->proplist, name);
1483  cpl_errorstate_set(prestate);
1484  }
1485  return cpl_error_set_message(cpl_func, cpl_error_get_code(),
1486  "Could not set '%s'. Likely the source property has a"
1487  " different format or type.", name);
1488  }
1489 
1490  return CPL_ERROR_NONE;
1491 }
1492 
1493 
1494 cpl_error_code irplib_sdp_spectrum_copy_property_regexp(
1495  irplib_sdp_spectrum *self,
1496  const cpl_propertylist *plist,
1497  const char *regexp,
1498  int invert)
1499 {
1500  cpl_propertylist *sublist = NULL;
1501  cpl_propertylist *origlist = NULL;
1502  cpl_errorstate prestate = cpl_errorstate_get();
1503  cpl_size i;
1504 
1505  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1506 
1507  assert(self->proplist != NULL);
1508 
1509  sublist = cpl_propertylist_new();
1510  origlist = cpl_propertylist_new();
1511  cpl_propertylist_copy_property_regexp(origlist, self->proplist, regexp,
1512  invert);
1513  cpl_propertylist_copy_property_regexp(sublist, plist, regexp, invert);
1514  if (cpl_propertylist_has(sublist, KEY_NELEM)) {
1515  /* Move the NELEM key to the end of the list so that rollback on error is
1516  * easier. */
1517  cpl_propertylist_erase(sublist, KEY_NELEM);
1518  cpl_propertylist_copy_property(sublist, plist, KEY_NELEM);
1519  }
1520  if (! cpl_errorstate_is_equal(prestate)) goto cleanup;
1521 
1522  for (i = 0; i < cpl_propertylist_get_size(sublist); ++i) {
1523  const cpl_property *p = cpl_propertylist_get_const(sublist, i);
1524  const char *name = cpl_property_get_name(p);
1525  irplib_sdp_spectrum_copy_keyword(self, sublist, name);
1526  if (! cpl_errorstate_is_equal(prestate)) goto cleanup;
1527  }
1528 
1529  cpl_propertylist_delete(sublist);
1530  cpl_propertylist_delete(origlist);
1531  return CPL_ERROR_NONE;
1532 
1533 cleanup:
1534  /* Cleanup here if an error occurred, by restoring the keywords to the
1535  * original values. */
1536  prestate = cpl_errorstate_get();
1537  (void) cpl_propertylist_copy_property_regexp(self->proplist, origlist,
1538  ".*", 0);
1539  cpl_errorstate_set(prestate);
1540  cpl_propertylist_delete(sublist);
1541  cpl_propertylist_delete(origlist);
1542  return cpl_error_get_code();
1543 }
1544 
1545 
1546 GET_SET_METHODS_TYPE_DOUBLE(ra, KEY_RA, KEY_RA_COMMENT)
1547 GET_SET_METHODS_TYPE_DOUBLE(dec, KEY_DEC, KEY_DEC_COMMENT)
1548 GET_SET_METHODS_TYPE_DOUBLE(exptime, KEY_EXPTIME, KEY_EXPTIME_COMMENT)
1549 GET_SET_METHODS_TYPE_DOUBLE(texptime, KEY_TEXPTIME, KEY_TEXPTIME_COMMENT)
1550 GET_SET_METHODS_TYPE_STRING(timesys, KEY_TIMESYS, KEY_TIMESYS_COMMENT)
1551 GET_SET_METHODS_TYPE_DOUBLE(mjdobs, KEY_MJDOBS, KEY_MJDOBS_COMMENT)
1552 GET_SET_METHODS_TYPE_DOUBLE(mjdend, KEY_MJDEND, KEY_MJDEND_COMMENT)
1553 GET_SET_METHODS_TYPE_INT(prodlvl, KEY_PRODLVL, KEY_PRODLVL_COMMENT)
1554 GET_SET_METHODS_TYPE_STRING(procsoft, KEY_PROCSOFT, KEY_PROCSOFT_COMMENT)
1555 GET_SET_METHODS_TYPE_STRING(prodcatg, KEY_PRODCATG, KEY_PRODCATG_COMMENT)
1556 GET_SET_METHODS_TYPE_STRING(origin, KEY_ORIGIN, KEY_ORIGIN_COMMENT)
1557 GET_SET_METHODS_TYPE_BOOL(extobj, KEY_EXT_OBJ, KEY_EXT_OBJ_COMMENT)
1558 GET_SET_METHODS_TYPE_STRING(dispelem, KEY_DISPELEM, KEY_DISPELEM_COMMENT)
1559 GET_SET_METHODS_TYPE_STRING(specsys, KEY_SPECSYS, KEY_SPECSYS_COMMENT)
1560 GET_SET_METHODS_TYPE_STRING(progid, KEY_PROG_ID, KEY_PROG_ID_COMMENT)
1561 GET_SET_ARRAY_METHODS_TYPE_INT(obid, KEY_OBID, KEY_OBID_COMMENT)
1562 GET_SET_METHODS_TYPE_BOOL(mepoch, KEY_M_EPOCH, KEY_M_EPOCH_COMMENT)
1563 GET_SET_METHODS_TYPE_STRING(obstech, KEY_OBSTECH, KEY_OBSTECH_COMMENT)
1564 GET_SET_METHODS_TYPE_STRING(fluxcal, KEY_FLUXCAL, KEY_FLUXCAL_COMMENT)
1565 GET_SET_METHODS_TYPE_BOOL(contnorm, KEY_CONTNORM, KEY_CONTNORM_COMMENT)
1566 GET_SET_METHODS_TYPE_DOUBLE(wavelmin, KEY_WAVELMIN, KEY_WAVELMIN_COMMENT)
1567 GET_SET_METHODS_TYPE_DOUBLE(wavelmax, KEY_WAVELMAX, KEY_WAVELMAX_COMMENT)
1568 GET_SET_METHODS_TYPE_DOUBLE(specbin, KEY_SPEC_BIN, KEY_SPEC_BIN_COMMENT)
1569 GET_SET_METHODS_TYPE_BOOL(totflux, KEY_TOT_FLUX, KEY_TOT_FLUX_COMMENT)
1570 GET_SET_METHODS_TYPE_DOUBLE(fluxerr, KEY_FLUXERR, KEY_FLUXERR_COMMENT)
1571 GET_SET_METHODS_TYPE_STRING(referenc, KEY_REFERENC, KEY_REFERENC_COMMENT)
1572 GET_SET_METHODS_TYPE_DOUBLE(specres, KEY_SPEC_RES, KEY_SPEC_RES_COMMENT)
1573 GET_SET_METHODS_TYPE_DOUBLE(specerr, KEY_SPEC_ERR, KEY_SPEC_ERR_COMMENT)
1574 GET_SET_METHODS_TYPE_DOUBLE(specsye, KEY_SPEC_SYE, KEY_SPEC_SYE_COMMENT)
1575 GET_SET_METHODS_TYPE_INT(lamnlin, KEY_LAMNLIN, KEY_LAMNLIN_COMMENT)
1576 GET_SET_METHODS_TYPE_DOUBLE(lamrms, KEY_LAMRMS, KEY_LAMRMS_COMMENT)
1577 GET_SET_METHODS_TYPE_DOUBLE(gain, KEY_GAIN, KEY_GAIN_COMMENT)
1578 GET_SET_METHODS_TYPE_DOUBLE(detron, KEY_DETRON, KEY_DETRON_COMMENT)
1579 GET_SET_METHODS_TYPE_DOUBLE(effron, KEY_EFFRON, KEY_EFFRON_COMMENT)
1580 GET_SET_METHODS_TYPE_DOUBLE(snr, KEY_SNR, KEY_SNR_COMMENT)
1581 GET_SET_METHODS_TYPE_INT(ncombine, KEY_NCOMBINE, KEY_NCOMBINE_COMMENT)
1582 GET_SET_ARRAY_METHODS_TYPE_STRING(prov, KEY_PROV, KEY_PROV_COMMENT)
1583 GET_SET_ARRAY_METHODS_TYPE_STRING(asson, KEY_ASSON, KEY_ASSON_COMMENT)
1584 GET_SET_ARRAY_METHODS_TYPE_STRING(assoc, KEY_ASSOC, KEY_ASSOC_COMMENT)
1585 GET_SET_ARRAY_METHODS_TYPE_STRING(assom, KEY_ASSOM, KEY_ASSOM_COMMENT)
1586 GET_SET_METHODS_TYPE_STRING(voclass, KEY_VOCLASS, KEY_VOCLASS_COMMENT)
1587 GET_SET_METHODS_TYPE_STRING(vopub, KEY_VOPUB, KEY_VOPUB_COMMENT)
1588 GET_SET_METHODS_TYPE_STRING(title, KEY_TITLE, KEY_TITLE_COMMENT)
1589 GET_SET_METHODS_TYPE_STRING(object, KEY_OBJECT, KEY_OBJECT_COMMENT)
1590 GET_SET_METHODS_TYPE_DOUBLE(aperture, KEY_APERTURE, KEY_APERTURE_COMMENT)
1591 GET_SET_METHODS_TYPE_DOUBLE(telapse, KEY_TELAPSE, KEY_TELAPSE_COMMENT)
1592 GET_SET_METHODS_TYPE_DOUBLE(tmid, KEY_TMID, KEY_TMID_COMMENT)
1593 GET_SET_METHODS_TYPE_DOUBLE(specval, KEY_SPEC_VAL, KEY_SPEC_VAL_COMMENT)
1594 GET_SET_METHODS_TYPE_DOUBLE(specbw, KEY_SPEC_BW, KEY_SPEC_BW_COMMENT)
1595 GET_SET_METHODS_TYPE_STRING(extname, KEY_EXTNAME, KEY_EXTNAME_COMMENT)
1596 GET_SET_METHODS_TYPE_BOOL(inherit, KEY_INHERIT, KEY_INHERIT_COMMENT)
1597 GET_SET_METHODS_TYPE_DOUBLE(tdmin, KEY_TDMIN(1), KEY_TDMIN1_COMMENT)
1598 GET_SET_METHODS_TYPE_DOUBLE(tdmax, KEY_TDMAX(1), KEY_TDMAX1_COMMENT)
1599 
1600 
1601 cpl_error_code irplib_sdp_spectrum_append_prov(irplib_sdp_spectrum *self,
1602  cpl_size firstindex,
1603  const cpl_frameset *frames)
1604 {
1605  cpl_frameset_iterator* iter = NULL;
1606  cpl_propertylist* keywords = NULL;
1607  const cpl_frame* frame;
1608  cpl_size index = firstindex;
1609 
1610  /* Note: check for NULL already done in irplib_sdp_spectrum_set_prov below. */
1611  assert(self != NULL);
1612  assert(self->proplist != NULL);
1613 
1614  iter = cpl_frameset_iterator_new(frames);
1615  frame = cpl_frameset_iterator_get_const(iter);
1616  while (frame != NULL) {
1617  cpl_error_code error;
1618  const char* value = NULL;
1619 
1620  /* Load the keywords from the raw frame. */
1621  const char* filename = cpl_frame_get_filename(frame);
1622  cpl_error_ensure(filename != NULL, cpl_error_get_code(), goto cleanup,
1623  "%s", cpl_error_get_message());
1624  keywords = cpl_propertylist_load(filename, 0);
1625  cpl_error_ensure(filename != NULL, cpl_error_get_code(), goto cleanup,
1626  "Could not load keywords from primary HDU in '%s'.",
1627  filename);
1628 
1629  /* Try set the value to ARCFILE or ORIGFILE or just the filename, whichever
1630  * is found first in that order. */
1631  if (cpl_propertylist_has(keywords, KEY_ARCFILE)) {
1632  value = cpl_propertylist_get_string(keywords, KEY_ARCFILE);
1633  cpl_error_ensure(value != NULL, cpl_error_get_code(), goto cleanup,
1634  "Could not extract the '%s' keyword value from '%s'.",
1635  KEY_ARCFILE, filename);
1636  } else if (cpl_propertylist_has(keywords, KEY_ORIGFILE)) {
1637  value = cpl_propertylist_get_string(keywords, KEY_ORIGFILE);
1638  cpl_error_ensure(value != NULL, cpl_error_get_code(), goto cleanup,
1639  "Could not extract the '%s' keyword value from '%s'.",
1640  KEY_ORIGFILE, filename);
1641  } else {
1642  value = filename;
1643  }
1644 
1645  /* Add the next PROVi keyword. */
1646  error = irplib_sdp_spectrum_set_prov(self, index, value);
1647  cpl_error_ensure(! error, error, goto cleanup,
1648  "%s", cpl_error_get_message());
1649  cpl_propertylist_delete(keywords);
1650  keywords = NULL;
1651 
1652  /* Increment the iterator to the next frame. */
1653  cpl_errorstate status = cpl_errorstate_get();
1654  cpl_frameset_iterator_advance(iter, 1);
1655  if (cpl_error_get_code() == CPL_ERROR_ACCESS_OUT_OF_RANGE) {
1656  cpl_errorstate_set(status);
1657  }
1658  frame = cpl_frameset_iterator_get_const(iter);
1659  ++index;
1660  }
1661 
1662  cpl_frameset_iterator_delete(iter);
1663  return CPL_ERROR_NONE;
1664 
1665 cleanup:
1666  /* Cleanup if an error occurs. Note: delete methods already check for NULL. */
1667  cpl_frameset_iterator_delete(iter);
1668  cpl_propertylist_delete(keywords);
1669  return cpl_error_get_code();
1670 }
1671 
1672 
1673 cpl_size irplib_sdp_spectrum_get_nelem(const irplib_sdp_spectrum *self)
1674 {
1675  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1676  return self->nelem;
1677 }
1678 
1679 
1680 cpl_error_code irplib_sdp_spectrum_reset_nelem(irplib_sdp_spectrum *self)
1681 {
1682  return irplib_sdp_spectrum_set_nelem(self, 0);
1683 }
1684 
1685 
1686 cpl_error_code irplib_sdp_spectrum_set_nelem(irplib_sdp_spectrum *self,
1687  cpl_size value)
1688 {
1689  cpl_size ncol;
1690  cpl_error_code error = CPL_ERROR_NONE;
1691 
1692  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1693 
1694  assert(self->table != NULL);
1695 
1696  ncol = cpl_table_get_ncol(self->table);
1697  if (ncol > 0) {
1698  /* Update all column depths. */
1699  cpl_size i;
1700  cpl_array *names = cpl_table_get_column_names(self->table);
1701  for (i = 0; i < ncol; ++i) {
1702  const char *name = cpl_array_get_string(names, i);
1703  error = cpl_table_set_column_depth(self->table, name, value);
1704  if (error) {
1705  /* If an error occurs then set the columns that were changed back to
1706  * the previous value. */
1707  cpl_size j;
1708  cpl_errorstate prestate = cpl_errorstate_get();
1709  for (j = 0; j < i; ++j) {
1710  (void) cpl_table_set_column_depth(self->table, name, self->nelem);
1711  }
1712  cpl_errorstate_set(prestate);
1713  break;
1714  }
1715  }
1716  cpl_array_delete(names);
1717  }
1718  if (! error) {
1719  self->nelem = value;
1720  }
1721  return error;
1722 }
1723 
1724 
1725 cpl_error_code irplib_sdp_spectrum_copy_nelem(irplib_sdp_spectrum *self,
1726  const cpl_propertylist *plist,
1727  const char *name)
1728 {
1729  /* Note: check for plist or name == NULL is done in cpl_propertylist calls. */
1730  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1731 
1732  assert(self->proplist != NULL);
1733 
1734  if (cpl_propertylist_has(plist, name)) {
1735  cpl_errorstate prestate = cpl_errorstate_get();
1736  cpl_size value = (cpl_size) cpl_propertylist_get_long_long(plist, name);
1737  if (cpl_errorstate_is_equal(prestate)) {
1738  return irplib_sdp_spectrum_set_nelem(self, value);
1739  } else {
1740  return cpl_error_set_message(cpl_func, cpl_error_get_code(),
1741  "Could not set '%s'. Likely the source '%s' keyword has a"
1742  " different format or type.", KEY_NELEM, name);
1743  }
1744  } else {
1745  return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
1746  "Could not set '%s' since the '%s' keyword was not found.",
1747  KEY_NELEM, name);
1748  }
1749 }
1750 
1751 
1752 cpl_size irplib_sdp_spectrum_get_ncol(const irplib_sdp_spectrum *self)
1753 {
1754  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1755  assert(self->table != NULL);
1756  return cpl_table_get_ncol(self->table);
1757 }
1758 
1759 
1760 cpl_boolean irplib_sdp_spectrum_has_column(const irplib_sdp_spectrum *self,
1761  const char* name)
1762 {
1763  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1764  assert(self->table != NULL);
1765  return cpl_table_has_column(self->table, name);
1766 }
1767 
1768 
1769 cpl_array *
1770 irplib_sdp_spectrum_get_column_names(const irplib_sdp_spectrum *self)
1771 {
1772  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, NULL);
1773  assert(self->table != NULL);
1774  return cpl_table_get_column_names(self->table);
1775 }
1776 
1777 
1778 cpl_error_code
1779 irplib_sdp_spectrum_new_column(irplib_sdp_spectrum *self, const char *name,
1780  cpl_type type)
1781 {
1782  cpl_error_code error;
1783  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1784  assert(self->table != NULL);
1785  error = cpl_table_new_column_array(self->table, name, type, self->nelem);
1786  if (error) {
1787  cpl_error_set_message(cpl_func, cpl_error_get_code(),
1788  "Failed to create a new column called '%s'.", name);
1789  }
1790  return error;
1791 }
1792 
1793 
1794 cpl_error_code
1795 irplib_sdp_spectrum_add_column(irplib_sdp_spectrum *self, const char *name,
1796  cpl_type type, const char *unit,
1797  const char *format, const char *tutyp,
1798  const char *tucd, const cpl_array *data)
1799 {
1800  cpl_error_code error;
1801 
1802  /* Note: check for name equals NULL should already be done in table calls. */
1803  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1804 
1805  assert(self->table != NULL);
1806 
1807  /* Setup a new array cell column and fill its properties (possibly with
1808  * defaults). */
1809  error = cpl_table_new_column_array(self->table, name, type, self->nelem);
1810  if (unit != NULL && *unit != '\0') {
1811  error |= cpl_table_set_column_unit(self->table, name, unit);
1812  } else {
1813  error |= cpl_table_set_column_unit(self->table, name, " ");
1814  }
1815  if (format != NULL) {
1816  error |= cpl_table_set_column_format(self->table, name, format);
1817  }
1818  if (tutyp != NULL) {
1819  error |= irplib_sdp_spectrum_set_column_tutyp(self, name, tutyp);
1820  } else {
1821  error |= irplib_sdp_spectrum_set_column_tutyp(self, name, "");
1822  }
1823  if (tucd != NULL) {
1824  error |= irplib_sdp_spectrum_set_column_tucd(self, name, tucd);
1825  } else {
1826  error |= irplib_sdp_spectrum_set_column_tucd(self, name, "");
1827  }
1828 
1829  /* Fill the table cell with the data array if available, else add an empty
1830  * array. */
1831  if (! error) {
1832  if (data != NULL) {
1833  error = cpl_table_set_array(self->table, name, 0, data);
1834  } else {
1835  cpl_array *array = cpl_array_new(self->nelem, type);
1836  if (array != NULL) {
1837  error = cpl_table_set_array(self->table, name, 0, array);
1838  cpl_array_delete(array);
1839  } else {
1840  error = cpl_error_get_code();
1841  }
1842  }
1843  }
1844 
1845  if (error) {
1846  /* Remove the column just added if there was an error. We initially save and
1847  * finally restore the error state since we might generate secondary errors
1848  * when trying to remove the partially created column. But these secondary
1849  * errors are expected and irrelevant. */
1850  cpl_errorstate prestate = cpl_errorstate_get();
1851  _irplib_sdp_spectrum_erase_column_keywords(self, name);
1852  (void) cpl_table_erase_column(self->table, name);
1853  cpl_errorstate_set(prestate);
1854  error = cpl_error_set_message(cpl_func, cpl_error_get_code(),
1855  "Failed to create a new column called '%s'.", name);
1856  }
1857 
1858  return error;
1859 }
1860 
1861 
1862 cpl_error_code
1863 irplib_sdp_spectrum_delete_column(irplib_sdp_spectrum *self, const char *name)
1864 {
1865  cpl_errorstate prestate = cpl_errorstate_get();
1866  cpl_error_code error = CPL_ERROR_NONE;
1867 
1868  cpl_ensure_code(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT);
1869 
1870  assert(self->table != NULL);
1871 
1872  _irplib_sdp_spectrum_erase_column_keywords(self, name);
1873  if (! cpl_errorstate_is_equal(prestate)) {
1874  error |= cpl_error_get_code();
1875  }
1876  error |= cpl_table_erase_column(self->table, name);
1877  if (error) {
1878  return cpl_error_get_code();
1879  } else {
1880  return CPL_ERROR_NONE;
1881  }
1882 }
1883 
1884 
1885 static cpl_error_code
1886 _irplib_sdp_spectrum_copy_column(irplib_sdp_spectrum *self, const char *to_name,
1887  const cpl_table* table, const char *from_name)
1888 {
1889  cpl_error_code error;
1890 
1891  assert(self != NULL);
1892  assert(self->table != NULL);
1893 
1894  error = cpl_table_duplicate_column(self->table, to_name, table, from_name);
1895  if (error) return error;
1896  error |= irplib_sdp_spectrum_set_column_tutyp(self, to_name, "");
1897  error |= irplib_sdp_spectrum_set_column_tucd(self, to_name, "");
1898  if (error) {
1899  /* Rollback changes if an error occurred. */
1900  cpl_errorstate prestate = cpl_errorstate_get();
1901  _irplib_sdp_spectrum_erase_column_keywords(self, to_name);
1902  (void) cpl_table_erase_column(self->table, to_name);
1903  cpl_errorstate_set(prestate);
1904  return cpl_error_get_code();
1905  }
1906  return CPL_ERROR_NONE;
1907 }
1908 
1909 
1910 cpl_error_code
1911 irplib_sdp_spectrum_copy_column(irplib_sdp_spectrum *self,
1912  const cpl_table* table, const char *name)
1913 {
1914  /* Note: check for table == NULL || name == NULL should already be done in the
1915  * cpl_table calls. */
1916  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1917  return _irplib_sdp_spectrum_copy_column(self, name, table, name);
1918 }
1919 
1920 
1921 cpl_error_code
1922 irplib_sdp_spectrum_copy_column_regexp(irplib_sdp_spectrum *self,
1923  const cpl_table* table,
1924  const char *regexp, int invert)
1925 {
1926  regex_t re;
1927  cpl_array *names = NULL;
1928  cpl_size n, i;
1929  int reg_error_code;
1930 
1931  /* Note: table == NULL is checked in the cpl_table calls. */
1932  cpl_ensure_code(self != NULL && regexp != NULL, CPL_ERROR_NULL_INPUT);
1933 
1934  assert(self->table != NULL);
1935 
1936  reg_error_code = regcomp(&re, regexp, REG_EXTENDED | REG_NOSUB);
1937  if (reg_error_code != 0) {
1938  return cpl_error_set_regex(CPL_ERROR_ILLEGAL_INPUT, reg_error_code, &re,
1939  "regexp='%s', invert=%d", regexp, invert);
1940  }
1941 
1942  /* Go through all column names in the table we are copying from and mark the
1943  * names the regular expression filters out as invalid. */
1944  names = cpl_table_get_column_names(table);
1945  n = cpl_array_get_size(names);
1946  for (i = 0; i < n; ++i) {
1947  int match;
1948  const char *namei = cpl_array_get_string(names, i);
1949  cpl_error_ensure(! cpl_table_has_column(self->table, namei),
1950  CPL_ERROR_ILLEGAL_OUTPUT, goto cleanup,
1951  "The column '%s' already exists in the spectrum.", namei);
1952  match = (regexec(&re, namei, 0, NULL, 0) == 0);
1953  if ((! match && ! invert) || (match && invert)) {
1954  cpl_array_set_invalid(names, i);
1955  }
1956  }
1957  /* Now copy only the valid columns. */
1958  for (i = 0; i < n; ++i) {
1959  if (cpl_array_is_valid(names, i)) {
1960  const char *namei = cpl_array_get_string(names, i);
1961  cpl_error_code error = _irplib_sdp_spectrum_copy_column(self, namei,
1962  table, namei);
1963  if (error) {
1964  cpl_errorstate prestate;
1965  cpl_size j;
1966  cpl_error_set_message(cpl_func, error, "Could not copy column '%s'.",
1967  namei);
1968  /* Remove any columns already added if we got an error copying any
1969  * column. */
1970  prestate = cpl_errorstate_get();
1971  for (j = 0; j < i; ++j) {
1972  namei = cpl_array_get_string(names, i);
1973  _irplib_sdp_spectrum_erase_column_keywords(self, namei);
1974  (void) cpl_table_erase_column(self->table, namei);
1975  }
1976  cpl_errorstate_set(prestate);
1977  goto cleanup;
1978  }
1979  }
1980  }
1981  cpl_array_delete(names);
1982  regfree(&re);
1983  return CPL_ERROR_NONE;
1984 
1985 cleanup:
1986  /* This is a cleanup section to delete objects when an error occurs. */
1987  cpl_array_delete(names);
1988  regfree(&re);
1989  return cpl_error_get_code();
1990 }
1991 
1992 
1993 cpl_error_code
1994 irplib_sdp_spectrum_update_column(irplib_sdp_spectrum *self, const char *name,
1995  const cpl_table* table, const char *colname,
1996  int flags)
1997 {
1998  char *orig_unit = NULL;
1999  char *orig_format = NULL;
2000  cpl_errorstate prestate = cpl_errorstate_get();
2001 
2002  /* Note: check for name, colname equals NULL should already be done in the
2003  * cpl_table calls. */
2004  cpl_ensure_code(self != NULL && table != NULL, CPL_ERROR_NULL_INPUT);
2005 
2006  assert(self->table != NULL);
2007 
2008  if (! cpl_table_has_column(self->table, name)) {
2009  /* The column does not exist in the spectrum so just copy it. */
2010  return _irplib_sdp_spectrum_copy_column(self, name, table, colname);
2011  }
2012 
2013  /* Make sure the source column exists. */
2014  if (! cpl_table_has_column(table, colname)) {
2015  return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2016  "Column '%s' not found in table.", colname);
2017  }
2018 
2019  /* Update the unit and format values if requested. Note, we copy the original
2020  * value to be able to restore it if an error occurs. */
2021  if (flags & IRPLIB_COLUMN_UNIT) {
2022  const char* unit = cpl_table_get_column_unit(table, colname);
2023  /* Prevent completely empty strings else cfitsio silently deletes the
2024  * keyword. */
2025  if (unit != NULL && *unit == '\0') {
2026  unit = " ";
2027  }
2028  orig_unit = cpl_strdup(cpl_table_get_column_unit(self->table, name));
2029  cpl_table_set_column_unit(self->table, name, unit);
2030  if (! cpl_errorstate_is_equal(prestate)) goto cleanup;
2031  }
2032  if (flags & IRPLIB_COLUMN_FORMAT) {
2033  orig_format = cpl_strdup(cpl_table_get_column_format(self->table, name));
2034  cpl_table_set_column_format(self->table, name,
2035  cpl_table_get_column_format(table, colname));
2036  if (! cpl_errorstate_is_equal(prestate)) goto cleanup;
2037  }
2038 
2039  /* Update the data array. Leave this to the last task since it is normally
2040  * cheaper to rollback changes to the unit and format strings if an error
2041  * occurs. */
2042  if (flags & IRPLIB_COLUMN_DATA) {
2043  if (cpl_table_get_column_type(self->table, name) !=
2044  cpl_table_get_column_type(table, colname)) {
2045  cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
2046  "The table column '%s' and spectrum column '%s' do not"
2047  " have the same types.", colname, name);
2048  goto cleanup;
2049  }
2050  if (cpl_table_get_column_depth(self->table, name) !=
2051  cpl_table_get_column_depth(table, colname)) {
2052  cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
2053  "The table column '%s' and spectrum column '%s' do not"
2054  " have the same dimensions.", colname, name);
2055  goto cleanup;
2056  }
2057  const cpl_array* data = cpl_table_get_array(table, colname, 0);
2058  if (data == NULL) goto cleanup;
2059  cpl_table_set_array(self->table, name, 0, data);
2060  if (! cpl_errorstate_is_equal(prestate)) goto cleanup;
2061  }
2062 
2063  cpl_free(orig_unit);
2064  cpl_free(orig_format);
2065  return CPL_ERROR_NONE;
2066 
2067 cleanup:
2068  /* Cleanup if error occurred by rolling back modifications. */
2069  prestate = cpl_errorstate_get();
2070  if (orig_unit != NULL) {
2071  (void) cpl_table_set_column_unit(self->table, name, orig_unit);
2072  cpl_free(orig_unit);
2073  }
2074  if (orig_format != NULL) {
2075  (void) cpl_table_set_column_format(self->table, name, orig_format);
2076  cpl_free(orig_format);
2077  }
2078  cpl_errorstate_set(prestate);
2079  return cpl_error_get_code();
2080 }
2081 
2082 
2083 cpl_type irplib_sdp_spectrum_get_column_type(const irplib_sdp_spectrum *self,
2084  const char *name)
2085 {
2086  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, CPL_TYPE_INVALID);
2087  assert(self->table != NULL);
2088  return cpl_table_get_column_type(self->table, name);
2089 }
2090 
2091 
2092 const char *
2093 irplib_sdp_spectrum_get_column_unit(const irplib_sdp_spectrum *self,
2094  const char *name)
2095 {
2096  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, NULL);
2097  assert(self->table != NULL);
2098  return cpl_table_get_column_unit(self->table, name);
2099 }
2100 
2101 /*----------------------------------------------------------------------------*/
2116 /*----------------------------------------------------------------------------*/
2117 cpl_error_code
2118 irplib_sdp_spectrum_set_column_unit(irplib_sdp_spectrum *self,
2119  const char *name, const char *unit)
2120 {
2121  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2122  assert(self->table != NULL);
2123  /* Prevent completely empty strings else cfitsio silently deletes the
2124  * keyword. */
2125  if (unit != NULL && *unit == '\0') {
2126  unit = " ";
2127  }
2128  return cpl_table_set_column_unit(self->table, name, unit);
2129 }
2130 
2131 
2132 cpl_error_code
2133 irplib_sdp_spectrum_copy_column_unit(irplib_sdp_spectrum *self,
2134  const char *name,
2135  const cpl_propertylist *plist,
2136  const char *key)
2137 {
2138  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2139 
2140  assert(self->table != NULL);
2141 
2142  if (cpl_propertylist_has(plist, key)) {
2143  cpl_errorstate prestate = cpl_errorstate_get();
2144  const char *value = cpl_propertylist_get_string(plist, key);
2145  if (cpl_errorstate_is_equal(prestate)) {
2146  /* Prevent completely empty strings else cfitsio silently deletes the
2147  * keyword. */
2148  if (value != NULL && *value == '\0') {
2149  value = " ";
2150  }
2151  return cpl_table_set_column_unit(self->table, name, value);
2152  } else {
2153  return cpl_error_set_message(cpl_func, cpl_error_get_code(),
2154  "Could not set the unit for column '%s'. Likely the source '%s'"
2155  " keyword is not a string.", name, key);
2156  }
2157  } else {
2158  return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2159  "Could not set the unit for column '%s' since the '%s' keyword"
2160  " was not found.", name, key);
2161  }
2162 }
2163 
2164 
2165 const char *
2166 irplib_sdp_spectrum_get_column_format(const irplib_sdp_spectrum *self,
2167  const char *name)
2168 {
2169  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, NULL);
2170  assert(self->table != NULL);
2171  return cpl_table_get_column_format(self->table, name);
2172 }
2173 
2174 
2175 cpl_error_code
2176 irplib_sdp_spectrum_set_column_format(irplib_sdp_spectrum *self,
2177  const char *name, const char *format)
2178 {
2179  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2180  assert(self->table != NULL);
2181  return cpl_table_set_column_format(self->table, name, format);
2182 }
2183 
2184 
2185 static cpl_size
2186 _irplib_sdp_spectrum_get_column_index(const irplib_sdp_spectrum *self,
2187  const char *name)
2188 {
2189  cpl_size i, n;
2190  cpl_array *names;
2191 
2192  assert(self != NULL);
2193  assert(self->table != NULL);
2194  assert(name != NULL);
2195 
2196  /* Try find the index number of the column. */
2197  names = cpl_table_get_column_names(self->table);
2198  n = cpl_array_get_size(names);
2199  for (i = 0; i < n; ++i) {
2200  const char *namei = cpl_array_get_string(names, i);
2201  if (strcmp(namei, name) == 0) {
2202  cpl_array_delete(names);
2203  return i;
2204  }
2205  }
2206  cpl_array_delete(names);
2207  return (cpl_size)-1;
2208 }
2209 
2210 
2211 static const char *
2212 _irplib_sdp_spectrum_get_column_keyword(const irplib_sdp_spectrum *self,
2213  const char *name, const char *keyword)
2214 {
2215  cpl_size index;
2216  const char *result = NULL;
2217 
2218  assert(self != NULL);
2219  assert(self->proplist != NULL);
2220  assert(name != NULL);
2221  assert(keyword != NULL);
2222 
2223  index = _irplib_sdp_spectrum_get_column_index(self, name);
2224  if (index != (cpl_size)-1) {
2225  /* If the index number was found then try return the property value. */
2226  char *propname = cpl_sprintf("%s%"CPL_SIZE_FORMAT, keyword, index+1);
2227  if (cpl_propertylist_has(self->proplist, propname)) {
2228  result = cpl_propertylist_get_string(self->proplist, propname);
2229  }
2230  cpl_free(propname);
2231  } else {
2232  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2233  "Could not find '%s' keyword for column '%s'.", keyword, name);
2234  }
2235  return result;
2236 }
2237 
2238 
2239 static cpl_error_code
2240 _irplib_sdp_spectrum_set_column_keyword(irplib_sdp_spectrum *self,
2241  const char *name,
2242  const char *value,
2243  const char *keyword,
2244  const char *comment)
2245 {
2246  cpl_size index;
2247  char *propname, *pcomment;
2248 
2249  assert(self != NULL);
2250  assert(self->proplist != NULL);
2251  assert(name != NULL);
2252  assert(keyword != NULL);
2253  assert(comment != NULL);
2254 
2255  index = _irplib_sdp_spectrum_get_column_index(self, name);
2256  /* If the index was not found then return an error message. */
2257  if (index == (cpl_size)-1) {
2258  return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2259  "Could not find '%s' keyword for column '%s'.", keyword, name);
2260  }
2261  /* Since the index number was found then try set or add the property value. */
2262  cpl_error_code error = CPL_ERROR_NONE;
2263  propname = cpl_sprintf("%s%"CPL_SIZE_FORMAT, keyword, index+1);
2264  pcomment = cpl_sprintf("%s%"CPL_SIZE_FORMAT, comment, index+1);
2265  if (cpl_propertylist_has(self->proplist, propname)) {
2266  if (value != NULL) {
2267  error = cpl_propertylist_set_string(self->proplist, propname, value);
2268  } else {
2269  (void) cpl_propertylist_erase(self->proplist, propname);
2270  }
2271  } else if (value != NULL) {
2272  error = cpl_propertylist_append_string(self->proplist, propname, value);
2273  if (! error) {
2274  error = cpl_propertylist_set_comment(self->proplist, propname,
2275  pcomment);
2276  if (error) {
2277  /* Delete entry if we could not set the comment to maintain a
2278  * consistent state */
2279  cpl_errorstate prestate = cpl_errorstate_get();
2280  (void) cpl_propertylist_erase(self->proplist, propname);
2281  cpl_errorstate_set(prestate);
2282  }
2283  }
2284  }
2285  cpl_free(propname);
2286  cpl_free(pcomment);
2287  return error;
2288 }
2289 
2290 
2291 static void
2292 _irplib_sdp_spectrum_erase_column_keywords(irplib_sdp_spectrum *self,
2293  const char *name)
2294 {
2295  cpl_size index;
2296 
2297  assert(self != NULL);
2298  assert(self->proplist != NULL);
2299  assert(name != NULL);
2300 
2301  index = _irplib_sdp_spectrum_get_column_index(self, name);
2302  if (index != (cpl_size)-1) {
2303  char *propname = cpl_sprintf("%s%"CPL_SIZE_FORMAT, KEY_TUTYP, index+1);
2304  cpl_propertylist_erase(self->proplist, propname);
2305  cpl_free(propname);
2306  propname = cpl_sprintf("%s%"CPL_SIZE_FORMAT, KEY_TUCD, index+1);
2307  cpl_propertylist_erase(self->proplist, propname);
2308  cpl_free(propname);
2309  propname = cpl_sprintf("%s%"CPL_SIZE_FORMAT, KEY_TCOMM, index+1);
2310  cpl_propertylist_erase(self->proplist, propname);
2311  cpl_free(propname);
2312  }
2313 }
2314 
2315 
2316 const char *
2317 irplib_sdp_spectrum_get_column_tutyp(const irplib_sdp_spectrum *self,
2318  const char *name)
2319 {
2320  cpl_errorstate prestate = cpl_errorstate_get();
2321  const char *result;
2322  cpl_ensure(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT, NULL);
2323  result = _irplib_sdp_spectrum_get_column_keyword(self, name, KEY_TUTYP);
2324  if (! cpl_errorstate_is_equal(prestate)) {
2325  cpl_error_set_where(cpl_func);
2326  }
2327  return result;
2328 }
2329 
2330 
2331 cpl_error_code
2332 irplib_sdp_spectrum_set_column_tutyp(irplib_sdp_spectrum *self,
2333  const char *name, const char *tutyp)
2334 {
2335  cpl_error_code error;
2336  cpl_ensure_code(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT);
2337  error = _irplib_sdp_spectrum_set_column_keyword(self, name, tutyp,
2338  KEY_TUTYP, KEY_TUTYP_COMMENT);
2339  if (error) {
2340  cpl_error_set_where(cpl_func);
2341  }
2342  return error;
2343 }
2344 
2345 
2346 cpl_error_code
2347 irplib_sdp_spectrum_copy_column_tutyp(irplib_sdp_spectrum *self,
2348  const char *name,
2349  const cpl_propertylist *plist,
2350  const char *key)
2351 {
2352  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2353 
2354  assert(self->table != NULL);
2355 
2356  if (cpl_propertylist_has(plist, key)) {
2357  cpl_errorstate prestate = cpl_errorstate_get();
2358  const char *value = cpl_propertylist_get_string(plist, key);
2359  if (cpl_errorstate_is_equal(prestate)) {
2360  return irplib_sdp_spectrum_set_column_tutyp(self, name, value);
2361  } else {
2362  cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name) + 1;
2363  return cpl_error_set_message(cpl_func, cpl_error_get_code(),
2364  "Could not set '%s%"CPL_SIZE_FORMAT"' for column '%s'. Likely"
2365  " the source '%s' keyword is not a string.",
2366  KEY_TUTYP, index, name, key);
2367  }
2368  } else {
2369  cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name) + 1;
2370  return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2371  "Could not set '%s%"CPL_SIZE_FORMAT"' for column '%s' since the"
2372  " '%s' keyword was not found.", KEY_TUTYP, index, name, key);
2373  }
2374 }
2375 
2376 
2377 const char *
2378 irplib_sdp_spectrum_get_column_tucd(const irplib_sdp_spectrum *self,
2379  const char *name)
2380 {
2381  cpl_errorstate prestate = cpl_errorstate_get();
2382  const char *result;
2383  cpl_ensure(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT, NULL);
2384  result = _irplib_sdp_spectrum_get_column_keyword(self, name, KEY_TUCD);
2385  if (! cpl_errorstate_is_equal(prestate)) {
2386  cpl_error_set_where(cpl_func);
2387  }
2388  return result;
2389 }
2390 
2391 
2392 cpl_error_code
2393 irplib_sdp_spectrum_set_column_tucd(irplib_sdp_spectrum *self,
2394  const char *name, const char *tucd)
2395 {
2396  cpl_error_code error;
2397  cpl_ensure_code(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT);
2398  error = _irplib_sdp_spectrum_set_column_keyword(self, name, tucd,
2399  KEY_TUCD, KEY_TUCD_COMMENT);
2400  if (error) {
2401  cpl_error_set_where(cpl_func);
2402  }
2403  return error;
2404 }
2405 
2406 
2407 cpl_error_code
2408 irplib_sdp_spectrum_copy_column_tucd(irplib_sdp_spectrum *self,
2409  const char *name,
2410  const cpl_propertylist *plist,
2411  const char *key)
2412 {
2413  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2414 
2415  assert(self->table != NULL);
2416 
2417  if (cpl_propertylist_has(plist, key)) {
2418  cpl_errorstate prestate = cpl_errorstate_get();
2419  const char *value = cpl_propertylist_get_string(plist, key);
2420  if (cpl_errorstate_is_equal(prestate)) {
2421  return irplib_sdp_spectrum_set_column_tucd(self, name, value);
2422  } else {
2423  cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name) + 1;
2424  return cpl_error_set_message(cpl_func, cpl_error_get_code(),
2425  "Could not set '%s%"CPL_SIZE_FORMAT"' for column '%s'. Likely"
2426  " the source '%s' keyword is not a string.",
2427  KEY_TUCD, index, name, key);
2428  }
2429  } else {
2430  cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name) + 1;
2431  return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2432  "Could not set '%s%"CPL_SIZE_FORMAT"' for column '%s' since the"
2433  " '%s' keyword was not found.", KEY_TUCD, index, name, key);
2434  }
2435 }
2436 
2437 
2438 const char *
2439 irplib_sdp_spectrum_get_column_tcomm(const irplib_sdp_spectrum *self,
2440  const char *name)
2441 {
2442  cpl_errorstate prestate = cpl_errorstate_get();
2443  const char *result;
2444  cpl_ensure(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT, NULL);
2445  result = _irplib_sdp_spectrum_get_column_keyword(self, name, KEY_TCOMM);
2446  if (! cpl_errorstate_is_equal(prestate)) {
2447  cpl_error_set_where(cpl_func);
2448  }
2449  return result;
2450 }
2451 
2452 
2453 cpl_error_code
2454 irplib_sdp_spectrum_set_column_tcomm(irplib_sdp_spectrum *self,
2455  const char *name, const char *tcomm)
2456 {
2457  cpl_error_code error;
2458  cpl_ensure_code(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT);
2459  error = _irplib_sdp_spectrum_set_column_keyword(self, name, tcomm,
2460  KEY_TCOMM, KEY_TCOMM_COMMENT);
2461  if (error) {
2462  cpl_error_set_where(cpl_func);
2463  }
2464  return error;
2465 }
2466 
2467 
2468 cpl_error_code
2469 irplib_sdp_spectrum_copy_column_tcomm(irplib_sdp_spectrum *self,
2470  const char *name,
2471  const cpl_propertylist *plist,
2472  const char *key)
2473 {
2474  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2475 
2476  assert(self->table != NULL);
2477 
2478  if (cpl_propertylist_has(plist, key)) {
2479  cpl_errorstate prestate = cpl_errorstate_get();
2480  const char *value = cpl_propertylist_get_string(plist, key);
2481  if (cpl_errorstate_is_equal(prestate)) {
2482  return irplib_sdp_spectrum_set_column_tcomm(self, name, value);
2483  } else {
2484  cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name) + 1;
2485  return cpl_error_set_message(cpl_func, cpl_error_get_code(),
2486  "Could not set '%s%"CPL_SIZE_FORMAT"' for column '%s'. Likely"
2487  " the source '%s' keyword is not a string.",
2488  KEY_TCOMM, index, name, key);
2489  }
2490  } else {
2491  cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name) + 1;
2492  return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2493  "Could not set '%s%"CPL_SIZE_FORMAT"' for column '%s' since the"
2494  " '%s' keyword was not found.", KEY_TCOMM, index, name, key);
2495  }
2496 }
2497 
2498 
2499 const cpl_array *
2500 irplib_sdp_spectrum_get_column_data(const irplib_sdp_spectrum *self,
2501  const char *name)
2502 {
2503  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, NULL);
2504  assert(self->table != NULL);
2505  return cpl_table_get_array(self->table, name, 0);
2506 }
2507 
2508 
2509 cpl_error_code
2510 irplib_sdp_spectrum_set_column_data(irplib_sdp_spectrum *self,
2511  const char *name, const cpl_array *array)
2512 {
2513  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2514  assert(self->table != NULL);
2515  return cpl_table_set_array(self->table, name, 0, array);
2516 }
2517 
2518 
2519 static char * _irplib_make_regexp(const cpl_propertylist *plist,
2520  const char *extra)
2521 {
2522  /* Minimum number of characters required for possible "^(", "|" or ")$"
2523  * fragments and a null character to end the string. */
2524  static const cpl_size min_chars_required = 6;
2525 
2526  /* Start, end and join fragments for the regular expression, to form a string
2527  * of the following form: "^(KEY1|KEY2 .. |KEYN)$" */
2528  static const char *start_fragment = "^(";
2529  static const char *end_fragment = ")$";
2530  static const char *join_fragment = "|";
2531 
2532  cpl_size extra_length = (extra != NULL ? (cpl_size) strlen(extra) : 0);
2533  cpl_size regexp_size, bytesleft, nkeys, i;
2534  char *writepos;
2535  char *regexp = NULL;
2536 
2537  assert(plist != NULL);
2538 
2539  nkeys = cpl_propertylist_get_size(plist);
2540  if (nkeys == 0) {
2541  /* Handle special case where plist is empty. */
2542  if (extra != NULL) {
2543  return cpl_sprintf("%s%s%s", start_fragment, extra, end_fragment);
2544  } else {
2545  return cpl_strdup("");
2546  }
2547  }
2548 
2549  /* Allocate enough space to store the regexp. 80 = FITS card width. */
2550  regexp_size = nkeys * 80 + min_chars_required + extra_length;
2551  regexp = cpl_malloc(regexp_size);
2552 
2553  bytesleft = regexp_size;
2554  writepos = regexp;
2555  for (i = 0; i < nkeys; ++i) {
2556  cpl_size name_length, fragment_length;
2557  const char *name, *fragment;
2558 
2559  /* Fetch the property name string. */
2560  const cpl_property *p = cpl_propertylist_get_const(plist, i);
2561  cpl_error_ensure(p != NULL, cpl_error_get_code(), goto cleanup,
2562  "Unexpected error accessing property structure %"CPL_SIZE_FORMAT".", i);
2563  name = cpl_property_get_name(p);
2564  cpl_error_ensure(name != NULL, cpl_error_get_code(), goto cleanup,
2565  "Unexpected error accessing the name of property %"CPL_SIZE_FORMAT".", i);
2566  name_length = (cpl_size) strlen(name);
2567 
2568  /* Figure out the regexp start/join string fragment to use. */
2569  fragment = (i == 0) ? start_fragment : join_fragment;
2570  fragment_length = (cpl_size) strlen(fragment);
2571 
2572  while (bytesleft <
2573  fragment_length + name_length + extra_length + min_chars_required)
2574  {
2575  /* Allocate more space if we still run out of space for the regexp.
2576  * Note: the realloc either succeeds or aborts on failure. */
2577  bytesleft += regexp_size;
2578  regexp_size += regexp_size;
2579  regexp = cpl_realloc(regexp, regexp_size);
2580  writepos = regexp + (regexp_size - bytesleft);
2581  }
2582 
2583  /* Write the start/join fragment and then the key name strings. */
2584  strncpy(writepos, fragment, bytesleft);
2585  bytesleft -= fragment_length;
2586  writepos += fragment_length;
2587  strncpy(writepos, name, bytesleft);
2588  bytesleft -= name_length;
2589  writepos += name_length;
2590  }
2591 
2592  /* Write the extra string and end fragment string to complete the regexp. */
2593  if (extra != NULL) {
2594  strncpy(writepos, join_fragment, bytesleft);
2595  bytesleft -= (cpl_size) strlen(join_fragment);
2596  writepos += (cpl_size) strlen(join_fragment);
2597  strncpy(writepos, extra, bytesleft);
2598  bytesleft -= extra_length;
2599  writepos += extra_length;
2600  }
2601  strncpy(writepos, end_fragment, bytesleft);
2602  /* Null terminate the string buffer for safety. */
2603  regexp[regexp_size-1] = '\0';
2604 
2605  return regexp;
2606 
2607 cleanup:
2608  /* Cleanup in case of error: */
2609  cpl_free(regexp);
2610  return NULL;
2611 }
2612 
2613 
2614 irplib_sdp_spectrum * irplib_sdp_spectrum_load(const char *filename)
2615 {
2616  cpl_error_code error;
2617  irplib_sdp_spectrum *obj;
2618  cpl_propertylist *plist = NULL;
2619  cpl_propertylist *tmpplist = NULL;
2620  cpl_table *table = NULL;
2621  cpl_array *names = NULL;
2622  cpl_array *emptyarray = NULL;
2623  cpl_size nelem, ext, i;
2624  char *regexp = NULL;
2625 
2626  cpl_ensure(filename != NULL, CPL_ERROR_NULL_INPUT, NULL);
2627 
2628  /* Load the property list from file, making sure the properties from the
2629  * primary HDU take precedence over those from the extension if any keywords
2630  * are duplicated. Note, we only load keywords known to the spectrum class. */
2631  plist = cpl_propertylist_load_regexp(filename, 0, ALL_KEYS_REGEXP, 0);
2632  cpl_error_ensure(plist != NULL, cpl_error_get_code(), goto cleanup,
2633  "Could not load property list from primary HDU when loading file '%s'.",
2634  filename);
2635 
2636  /* We have to create a regexp to filter out keywords already loaded from the
2637  * primary HDU. */
2638  regexp = _irplib_make_regexp(plist, NULL);
2639  cpl_error_ensure(regexp != NULL, cpl_error_get_code(), goto cleanup,
2640  "Could not create regular expression to filter keywords.");
2641 
2642  /* Try find the spectrum extension from which to load the table. If the
2643  * extension name cannot be found then just use the first extension. */
2644  ext = cpl_fits_find_extension(filename, KEY_EXTNAME_VALUE);
2645  cpl_error_ensure(ext != (cpl_size)-1, cpl_error_get_code(), goto cleanup,
2646  "Failed to get the extension '%s' from file '%s'.",
2647  KEY_EXTNAME_VALUE, filename);
2648  if (ext == 0) ext = 1;
2649 
2650  /* Load only the SDP keywords from the extension. */
2651  tmpplist = cpl_propertylist_load_regexp(filename, ext, ALL_KEYS_REGEXP, 0);
2652  cpl_error_ensure(tmpplist != NULL, cpl_error_get_code(), goto cleanup,
2653  "Could not load property list from extension %"
2654  CPL_SIZE_FORMAT" when loading file '%s'.", ext, filename);
2655 
2656  /* Append keywords to plist that are not already in plist. */
2657  error = cpl_propertylist_copy_property_regexp(plist, tmpplist, regexp, 1);
2658  cpl_error_ensure(! error, error, goto cleanup,
2659  "Failed to append keywords from file '%s' extension %"
2660  CPL_SIZE_FORMAT".", filename, ext);
2661 
2662  /* Delete temporary objects that are no longer needed. */
2663  cpl_propertylist_delete(tmpplist);
2664  tmpplist = NULL;
2665  cpl_free(regexp);
2666  regexp = NULL;
2667 
2668  table = cpl_table_load(filename, (int)ext, CPL_TRUE);
2669  cpl_error_ensure(table != NULL, cpl_error_get_code(), goto cleanup,
2670  "Could not load the spectrum table from extension %"
2671  CPL_SIZE_FORMAT" when loading file '%s'.", ext, filename);
2672 
2673  /* Set the nelem value from the NELEM keyword if found, else work it out. */
2674  if (cpl_propertylist_has(plist, KEY_NELEM)) {
2675  cpl_errorstate prestate = cpl_errorstate_get();
2676  nelem = (cpl_size) cpl_propertylist_get_long_long(plist, KEY_NELEM);
2677  /* Remove NELEM since the value is instead stored in the nelem variable. */
2678  cpl_propertylist_erase(plist, KEY_NELEM);
2679  cpl_error_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(),
2680  goto cleanup, "Could not process the temporary '%s' keyword.",
2681  KEY_NELEM);
2682  } else {
2683  cpl_msg_warning(cpl_func,
2684  "Keyword '%s' not found in file '%s'. Possibly corrupted."
2685  " Will try find correct value from the table and continue.",
2686  KEY_NELEM, filename);
2687  nelem = 0;
2688  if (cpl_table_get_nrow(table) > 0) {
2689  names = cpl_table_get_column_names(table);
2690  if (names != NULL) {
2691  if (cpl_array_get_size(names) > 0) {
2692  const char *name = cpl_array_get_string(names, 0);
2693  nelem = cpl_table_get_column_depth(table, name);
2694  }
2695  cpl_array_delete(names);
2696  names = NULL;
2697  }
2698  }
2699  }
2700 
2701  names = cpl_table_get_column_names(table);
2702  cpl_error_ensure(names != NULL, cpl_error_get_code(), goto cleanup,
2703  "Could not get table column names when loading file '%s'.", filename);
2704  for (i = 0; i < cpl_array_get_size(names); ++i) {
2705  int j;
2706  const char *name = cpl_array_get_string(names, 0);
2707  cpl_type type = cpl_table_get_column_type(table, name);
2708  if ((type & CPL_TYPE_POINTER) == 0) continue; /* Only handle array columns.*/
2709  for (j = 0; j < cpl_table_get_nrow(table); ++j) {
2710  if (cpl_table_get_array(table, name, j) != NULL) continue;
2711  emptyarray = cpl_array_new(nelem, type & (~CPL_TYPE_POINTER));
2712  cpl_error_ensure(emptyarray != NULL, cpl_error_get_code(), goto cleanup,
2713  "Could not create empty array when spectrum table from file '%s'.",
2714  filename);
2715  error = cpl_table_set_array(table, name, j, emptyarray);
2716  cpl_array_delete(emptyarray);
2717  emptyarray = NULL;
2718  }
2719  }
2720  cpl_array_delete(names);
2721 
2722  /* Create new spectrum instance and return it. */
2723  obj = cpl_malloc(sizeof(irplib_sdp_spectrum));
2724  obj->nelem = nelem;
2725  obj->proplist = plist;
2726  obj->table = table;
2727  return obj;
2728 
2729 cleanup:
2730  /* Perform memory cleanup if an error occurred. The cpl_error_ensure macros
2731  * will send the control flow to this point when an error is detected.
2732  * Note: cpl_*_delete functions already check for NULL pointers. */
2733  cpl_propertylist_delete(plist);
2734  cpl_propertylist_delete(tmpplist);
2735  cpl_table_delete(table);
2736  cpl_array_delete(names);
2737  cpl_array_delete(emptyarray);
2738  cpl_free(regexp);
2739  return NULL;
2740 }
2741 
2742 
2743 cpl_error_code irplib_sdp_spectrum_save(const irplib_sdp_spectrum *self,
2744  const char *filename,
2745  const cpl_propertylist *extra_pheader,
2746  const cpl_propertylist *extra_theader)
2747 {
2748  cpl_error_code error;
2749  cpl_propertylist *primarykeys = NULL;
2750  cpl_propertylist *tablekeys = NULL;
2751  char *regexp = NULL;
2752 
2753  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2754 
2755  assert(self->proplist != NULL);
2756  assert(self->table != NULL);
2757 
2758  /* Make a regular expression to filter out all keywords found in the spectrum
2759  * object's proplist and NELEM from the extra header keywords. */
2760  regexp = _irplib_make_regexp(self->proplist, KEY_NELEM);
2761  cpl_error_ensure(regexp != NULL, cpl_error_get_code(), goto cleanup,
2762  "Could not create regular expression to filter keywords.");
2763 
2764  /* Copy out keywords that should be in the primary HDU header from the full
2765  * list of keywords in proplist. */
2766  primarykeys = cpl_propertylist_new();
2767  error = cpl_propertylist_copy_property_regexp(primarykeys, self->proplist,
2768  PRIMARY_HDU_KEYS_REGEXP, 0);
2769  cpl_error_ensure(! error, error, goto cleanup,
2770  "Failed to extract keywords for primary HDU.");
2771 
2772  /* Use a different comment name for OBJECT in the primary HDU to more closely
2773  * follow standard document. */
2774  if (cpl_propertylist_has(primarykeys, KEY_OBJECT)) {
2775  error = cpl_propertylist_set_comment(primarykeys, KEY_OBJECT,
2776  KEY_OBJECT_PHDU_COMMENT);
2777  cpl_error_ensure(! error, error, goto cleanup,
2778  "Could not update comment for '%s' in primary HDU.", KEY_OBJECT);
2779  }
2780 
2781  /* Copy any extra keywords that are not already in the primary HDU header. */
2782  if (extra_pheader != NULL) {
2783  error = cpl_propertylist_copy_property_regexp(primarykeys, extra_pheader,
2784  regexp, 1);
2785  cpl_error_ensure(! error, error, goto cleanup,
2786  "Could not add extra keywords for primary HDU.");
2787  }
2788 
2789  /* Copy out keywords for the table header from all in proplist. */
2790  tablekeys = cpl_propertylist_new();
2791  error = cpl_propertylist_copy_property_regexp(tablekeys, self->proplist,
2792  EXTENSION_HDU_KEYS_REGEXP, 0);
2793  cpl_error_ensure(! error, error, goto cleanup,
2794  "Failed to extract keywords for extension HDU.");
2795 
2796  /* Add the NELEM keyword from the nelem variable. */
2797  cpl_error_ensure(self->nelem <= INT_MAX, CPL_ERROR_INCOMPATIBLE_INPUT,
2798  goto cleanup,
2799  "The value for the keyword '%s' is too big (> %d).",
2800  KEY_NELEM, INT_MAX);
2801  error = cpl_propertylist_append_int(tablekeys, KEY_NELEM,
2802  (int) self->nelem);
2803  error |= cpl_propertylist_set_comment(tablekeys, KEY_NELEM,
2804  KEY_NELEM_COMMENT);
2805  cpl_error_ensure(! error, error, goto cleanup,
2806  "Could not add keyword '%s' to primary HDU or set the comment.",
2807  KEY_NELEM);
2808 
2809  /* Copy extra keywords that are not already in the extension HDU header. */
2810  if (extra_theader != NULL) {
2811  error = cpl_propertylist_copy_property_regexp(tablekeys, extra_theader,
2812  regexp, 1);
2813  cpl_error_ensure(! error, error, goto cleanup,
2814  "Could not add extra keywords for extension HDU.");
2815  }
2816 
2817  cpl_free(regexp);
2818  regexp = NULL;
2819 
2820  /* Add some mandatory keywords with default values that are still not found
2821  * in the primary or extension property lists, since they were not set in the
2822  * spectrum or in the extra header lists. */
2823  error = CPL_ERROR_NONE;
2824  if (! cpl_propertylist_has(primarykeys, KEY_ORIGIN)) {
2825  error |= cpl_propertylist_append_string(primarykeys, KEY_ORIGIN,
2826  KEY_ORIGIN_VALUE);
2827  error |= cpl_propertylist_set_comment(primarykeys, KEY_ORIGIN,
2828  KEY_ORIGIN_COMMENT);
2829  }
2830  if (! cpl_propertylist_has(primarykeys, KEY_PRODLVL)) {
2831  error |= cpl_propertylist_append_int(primarykeys, KEY_PRODLVL,
2832  KEY_PRODLVL_VALUE);
2833  error |= cpl_propertylist_set_comment(primarykeys, KEY_PRODLVL,
2834  KEY_PRODLVL_COMMENT);
2835  }
2836  if (! cpl_propertylist_has(primarykeys, KEY_SPECSYS)) {
2837  error |= cpl_propertylist_append_string(primarykeys, KEY_SPECSYS,
2838  KEY_SPECSYS_VALUE);
2839  error |= cpl_propertylist_set_comment(primarykeys, KEY_SPECSYS,
2840  KEY_SPECSYS_COMMENT);
2841  }
2842  if (! cpl_propertylist_has(primarykeys, KEY_FLUXERR)) {
2843  error |= cpl_propertylist_append_int(primarykeys, KEY_FLUXERR,
2844  KEY_FLUXERR_VALUE);
2845  error |= cpl_propertylist_set_comment(primarykeys, KEY_FLUXERR,
2846  KEY_FLUXERR_COMMENT);
2847  }
2848  if (! cpl_propertylist_has(tablekeys, KEY_VOCLASS)) {
2849  error |= cpl_propertylist_append_string(tablekeys, KEY_VOCLASS,
2850  KEY_VOCLASS_VALUE);
2851  error |= cpl_propertylist_set_comment(tablekeys, KEY_VOCLASS,
2852  KEY_VOCLASS_COMMENT);
2853  }
2854  if (! cpl_propertylist_has(tablekeys, KEY_VOPUB)) {
2855  error |= cpl_propertylist_append_string(tablekeys, KEY_VOPUB,
2856  KEY_VOPUB_VALUE);
2857  error |= cpl_propertylist_set_comment(tablekeys, KEY_VOPUB,
2858  KEY_VOPUB_COMMENT);
2859  }
2860  if (! cpl_propertylist_has(tablekeys, KEY_EXTNAME)) {
2861  error |= cpl_propertylist_append_string(tablekeys, KEY_EXTNAME,
2862  KEY_EXTNAME_VALUE);
2863  error |= cpl_propertylist_set_comment(tablekeys, KEY_EXTNAME,
2864  KEY_EXTNAME_COMMENT);
2865  }
2866  if (! cpl_propertylist_has(tablekeys, KEY_INHERIT)) {
2867  error |= cpl_propertylist_append_bool(tablekeys, KEY_INHERIT,
2868  KEY_INHERIT_VALUE);
2869  error |= cpl_propertylist_set_comment(tablekeys, KEY_INHERIT,
2870  KEY_INHERIT_COMMENT);
2871  }
2872  cpl_error_ensure(! error, cpl_error_get_code(), goto cleanup,
2873  "Could not set default header keywords for file '%s'.",
2874  filename);
2875 
2876  error = cpl_table_save(self->table, primarykeys, tablekeys, filename,
2877  CPL_IO_CREATE);
2878  cpl_error_ensure(! error, error, goto cleanup,
2879  "Could not save the spectrum table to file '%s'.", filename);
2880 
2881  cpl_propertylist_delete(primarykeys);
2882  cpl_propertylist_delete(tablekeys);
2883 
2884  return CPL_ERROR_NONE;
2885 
2886 cleanup:
2887  /* Cleanup memory if an error occurred. Note: cpl_*_delete functions already
2888  * check for NULL pointers. */
2889  cpl_propertylist_delete(primarykeys);
2890  cpl_propertylist_delete(tablekeys);
2891  cpl_free(regexp);
2892  return cpl_error_get_code();
2893 }
2894 
2895 
2896 cpl_error_code irplib_dfs_save_spectrum(cpl_frameset * allframes,
2897  cpl_propertylist * header,
2898  const cpl_parameterlist * parlist,
2899  const cpl_frameset * usedframes,
2900  const cpl_frame * inherit,
2901  const irplib_sdp_spectrum * spectrum,
2902  const char * recipe,
2903  const cpl_propertylist * applist,
2904  const cpl_propertylist * tablelist,
2905  const char * remregexp,
2906  const char * pipe_id,
2907  const char * dict_id,
2908  const char * filename)
2909 {
2910  const char * procat;
2911  cpl_propertylist * plist = NULL;
2912  cpl_frame * product_frame = NULL;
2913  cpl_error_code error;
2914 
2915  cpl_ensure_code(allframes != NULL, CPL_ERROR_NULL_INPUT);
2916  cpl_ensure_code(parlist != NULL, CPL_ERROR_NULL_INPUT);
2917  cpl_ensure_code(usedframes != NULL, CPL_ERROR_NULL_INPUT);
2918  cpl_ensure_code(spectrum != NULL, CPL_ERROR_NULL_INPUT);
2919  cpl_ensure_code(recipe != NULL, CPL_ERROR_NULL_INPUT);
2920  cpl_ensure_code(applist != NULL, CPL_ERROR_NULL_INPUT);
2921  cpl_ensure_code(pipe_id != NULL, CPL_ERROR_NULL_INPUT);
2922  cpl_ensure_code(dict_id != NULL, CPL_ERROR_NULL_INPUT);
2923  cpl_ensure_code(filename != NULL, CPL_ERROR_NULL_INPUT);
2924 
2925  procat = cpl_propertylist_get_string(applist, CPL_DFS_PRO_CATG);
2926  cpl_error_ensure(procat != NULL, cpl_error_get_code(), goto cleanup,
2927  "Could not find keyword '%s' in 'applist'.", CPL_DFS_PRO_CATG);
2928 
2929  /* Create product frame */
2930  product_frame = cpl_frame_new();
2931  error = cpl_frame_set_filename(product_frame, filename);
2932  error |= cpl_frame_set_tag(product_frame, procat);
2933  error |= cpl_frame_set_type(product_frame, CPL_FRAME_TYPE_TABLE);
2934  error |= cpl_frame_set_group(product_frame, CPL_FRAME_GROUP_PRODUCT);
2935  error |= cpl_frame_set_level(product_frame, CPL_FRAME_LEVEL_FINAL);
2936  cpl_error_ensure(! error, cpl_error_get_code(), goto cleanup,
2937  "Failed to setup the product frame.");
2938 
2939  /* Check if we should return the header information actually filled or just
2940  * create a temporary local list. */
2941  if (header != NULL) {
2942  cpl_propertylist_empty(header);
2943  plist = header;
2944  } else {
2945  plist = cpl_propertylist_new();
2946  }
2947 
2948  /* Add any QC parameters here. */
2949  error = cpl_propertylist_append(plist, applist);
2950  cpl_error_ensure(! error, error, goto cleanup,
2951  "Could not append extra keywords when writing file '%s'.", filename);
2952 
2953  /* Add DataFlow keywords. */
2954  error = cpl_dfs_setup_product_header(plist, product_frame, usedframes,
2955  parlist, recipe, pipe_id, dict_id,
2956  inherit);
2957  cpl_error_ensure(! error, error, goto cleanup,
2958  "Failed to setup DFS keywords when writing file '%s'.", filename);
2959 
2960  /* We have to update the extra keywords again for the primary HDU to make
2961  * sure we have the ability to override what cpl_dfs_setup_product_header
2962  * sets. The reason for still having the cpl_propertylist_append above is to
2963  * make sure we use comments as given by the applist and not as found in the
2964  * raw file we inherit from. The SDP format prefers standardised comments, not
2965  * necessarily used by the raw files. */
2966  error = cpl_propertylist_copy_property_regexp(plist, applist, ".*", 0);
2967  cpl_error_ensure(! error, error, goto cleanup,
2968  "Could not update extra keywords when writing file '%s'.", filename);
2969 
2970  if (remregexp != NULL) {
2971  cpl_errorstate prestate = cpl_errorstate_get();
2972  (void) cpl_propertylist_erase_regexp(plist, remregexp, 0);
2973  cpl_error_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(),
2974  goto cleanup,
2975  "Failed to filter keywords when writing file '%s'.",
2976  filename);
2977  }
2978 
2979  error = irplib_sdp_spectrum_save(spectrum, filename, plist, tablelist);
2980  cpl_error_ensure(! error, error, goto cleanup,
2981  "Failed to save SPD spectrum to file '%s'.", filename);
2982 
2983  /* Optionally return the SDP keywords that were written to the output. */
2984  if (header != NULL) {
2985  error = cpl_propertylist_copy_property_regexp(header, spectrum->proplist,
2986  ".*", 0);
2987  cpl_error_ensure(! error, error, goto cleanup,
2988  "Could not return SDP keywords in header output.");
2989  }
2990 
2991  /* Insert the frame of the saved file in the input frameset. */
2992  error = cpl_frameset_insert(allframes, product_frame);
2993  cpl_error_ensure(! error, error, goto cleanup,
2994  "Failed to insert new product frame when writing file '%s'.", filename);
2995 
2996  /* Delete output property list if it was only a temporary local object. */
2997  if (plist != header) cpl_propertylist_delete(plist);
2998 
2999  return CPL_ERROR_NONE;
3000 
3001 cleanup:
3002  /* If an error occurred we come here to cleanup memory. Note that the delete
3003  * functions already check for NULL pointers. */
3004  if (header != NULL) {
3005  cpl_errorstate prestate = cpl_errorstate_get();
3006  (void) cpl_propertylist_empty(header);
3007  cpl_errorstate_set(prestate);
3008  } else {
3009  cpl_propertylist_delete(plist);
3010  }
3011  cpl_frame_delete(product_frame);
3012  return cpl_error_get_code();
3013 }
3014 
3015 
3016 void irplib_sdp_spectrum_dump(const irplib_sdp_spectrum *self, FILE *stream)
3017 {
3018  if (stream == NULL) {
3019  stream = stdout;
3020  }
3021  if (self == NULL) {
3022  fprintf(stream, "NULL SDP spectrum\n\n");
3023  return;
3024  }
3025 
3026  assert(self->proplist != NULL);
3027  assert(self->table != NULL);
3028 
3029  fprintf(stream, "SDP spectrum at address %p\n", (void*)self);
3030  fprintf(stream, "NELEM = %"CPL_SIZE_FORMAT"\n", self->nelem);
3031  cpl_propertylist_dump(self->proplist, stream);
3032  cpl_table_dump_structure(self->table, stream);
3033  cpl_table_dump(self->table, 0, cpl_table_get_nrow(self->table), stream);
3034 }
3035 
3036 
3037 #ifdef IRPLIB_USE_FITS_UPDATE_CHECKSUM
3038 
3050 cpl_error_code irplib_fits_update_checksums(const char* filename)
3051 {
3052  fitsfile* filehandle;
3053  int error = 0; /* must be initialised to zero before call to cfitsio. */
3054 
3055  if (fits_open_diskfile(&filehandle, filename, READWRITE, &error)) {
3056  return cpl_error_set_message(cpl_func, CPL_ERROR_FILE_IO,
3057  "Could not open file '%s' to update CHECKSUM keywords"
3058  " (error = %d).", filename, error);
3059  }
3060 
3061  int i = 0;
3062  while (! fits_movabs_hdu(filehandle, ++i, NULL, &error)) {
3063  if (fits_write_chksum(filehandle, &error)) {
3064  return cpl_error_set_message(cpl_func, CPL_ERROR_FILE_IO,
3065  "Could not update the CHECKSUM keywords in '%s' HDU %d"
3066  " (error = %d).", filename, i, error);
3067  }
3068  }
3069  /* Reset after normal error */
3070  if (error == END_OF_FILE) error = 0;
3071 
3072  if (fits_close_file(filehandle, &error)) {
3073  return cpl_error_set_message(cpl_func, CPL_ERROR_FILE_IO,
3074  "There was a problem trying to close the file '%s'"
3075  " (error = %d).", filename, error);
3076  }
3077  return CPL_ERROR_NONE;
3078 }
3079 
3080 #endif /* IRPLIB_USE_FITS_UPDATE_CHECKSUM */
struct _irplib_sdp_spectrum_ irplib_sdp_spectrum
Data type for a Science Data Product 1D spectrum.