GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: src/nrrd/formatNRRD.c Lines: 171 349 49.0 %
Date: 2017-05-26 Branches: 132 302 43.7 %

Line Branch Exec Source
1
/*
2
  Teem: Tools to process and visualize scientific data and images             .
3
  Copyright (C) 2015, 2014, 2013, 2012, 2011, 2010, 2009  University of Chicago
4
  Copyright (C) 2008, 2007, 2006, 2005  Gordon Kindlmann
5
  Copyright (C) 2004, 2003, 2002, 2001, 2000, 1999, 1998  University of Utah
6
7
  This library is free software; you can redistribute it and/or
8
  modify it under the terms of the GNU Lesser General Public License
9
  (LGPL) as published by the Free Software Foundation; either
10
  version 2.1 of the License, or (at your option) any later version.
11
  The terms of redistributing and/or modifying this software also
12
  include exceptions to the LGPL that facilitate static linking.
13
14
  This library is distributed in the hope that it will be useful,
15
  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
  Lesser General Public License for more details.
18
19
  You should have received a copy of the GNU Lesser General Public License
20
  along with this library; if not, write to Free Software Foundation, Inc.,
21
  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22
*/
23
24
#include "nrrd.h"
25
#include "privateNrrd.h"
26
27
/***
28
**** Info about string handling for fields
29
30
The Nrrd format can include strings in different fields, with different
31
rules for each one; see http://teem.sourceforge.net/nrrd/format.html Below
32
is some documentation of how the strings are handled, which is mostly a
33
documentation of old (long-established) code, the gathering of which
34
uncovered some problems and led to some new code (as of Thu Aug 23 09:32:11
35
CDT 2012).  Conceptual inconsistencies in the different handlings of strings
36
merit further review, including updating the file format spec (e.g. what
37
non-ASCII characters can be allowed where?  Unicode?  what encoding? etc).
38
This all also highlights the need for having a completely uniform way of
39
setting these fields at one-time via the nrrd library (e.g. can use
40
nrrdAxisInfoSet for "units" but there is no API for setting "space units").
41
Should that API flag as error if you try to include characters that can't
42
be losslessly saved, or should it silently transform things?
43
44
** Comments:
45
On disk, delimited by the NRRD_COMMENT_CHAR ('#') and the
46
end of the line, but the format spec doesn't address any escaping.
47
Input comments processed via:
48
  nrrd/formatNrrd.c/_nrrdFormatNRRD_read()
49
  --> nrrd/parseNrrd.c/_nrrdReadNrrdParse_comment()
50
      --> nrrd/comment.c/nrrdCommentAdd()
51
           --> air/string.c/airOneLinify()
52
On write, output comments processed via:
53
  nrrd/formatNrrd.c/_nrrdFormatNRRD_write()
54
  --> air/string.c/airOneLinify()
55
==> There is no escaping of anything: white-space is compressed into
56
a single ' '.  Probably justified justified given format spec.
57
58
** Content: On disk, finished with the end of line.  No mention
59
of escaping in the format spec. Input content processed via:
60
  nrrd/formatNrrd.c/_nrrdFormatNRRD_read()
61
  --> nrrd/parseNrrd.c/_nrrdReadNrrdParse_content()
62
      which does NO processing, just airStrdup
63
On write, output content processed via:
64
  nrrd/formatNrrd.c/_nrrdFormatNRRD_write()
65
  --> nrrd/write.c/_nrrdSprintFieldInfo()  (maybe via _nrrdFprintFieldInfo())
66
      --> air/string.c/airOneLinify()
67
==> not only is there no escaping, but there's some assymmetry in the
68
use of airOneLinify.  If information is being encoded in the number of
69
contiguous spaces in the content, its preserved on input but not on
70
output.  Still, there's no chance of writing a broken file.
71
72
** key/value pairs: The keys and values are separated by ":=", and the
73
format spec says (string) "\n" means (character) '\n' and "\\" means '\\'.
74
On input, both keys and values processed via:
75
  nrrd/formatNrrd.c/_nrrdFormatNRRD_read()
76
  --> nrrd/parseNrrd.c/_nrrdReadNrrdParse_keyvalue()
77
     --> air/string.c/airUnescape(), which deals with "\n" and "\\" ONLY
78
         (not quotes, not other whitespace), and then
79
     --> nrrd/keyvalue.c/nrrdKeyValueAdd(),
80
         which only does an airStrdup
81
On output, keys and values processed via
82
  nrrd/formatNrrd.c/_nrrdFormatNRRD_write()
83
  --> nrrd/keyvalue.c/_nrrdKeyValueWrite()
84
      --> nrrd/keyvalue.c/_nrrdWriteEscaped()
85
        which is invoked to escape \n and \,
86
        and (NOTE!) to convert all other whitespace to ' '
87
Aside from the file format spec, the nrrd *library* does not really have
88
any strictures about the characters that are allowed at run-time in key/values
89
(and indeed nrrdKeyValueAdd just does an airStrdup). But without
90
converting or escaping, say, '\r', you'll generate a broken NRRD file,
91
hence the new handling of converting other whitespace to ' '.
92
93
** labels and units: A "-delimited string per axis.
94
Format spec is very specific for labels, and implies units are the same:
95
"Within each label, double quotes may be included by escaping them
96
(\"), but no other form of escaping is supported".  On input:
97
  nrrd/formatNrrd.c/_nrrdFormatNRRD_read()
98
  --> nrrd/parseNrrd.c/_nrrdReadNrrdParse_labels()
99
                    or _nrrdReadNrrdParse_units()
100
      --> nrrd/parseNrrd.c/_nrrdGetQuotedString()
101
          which does the work of unescaping \"
102
On output:
103
  nrrd/formatNrrd.c/_nrrdFormatNRRD_write()
104
  --> nrrd/write.c/_nrrdSprintFieldInfo()  (maybe via _nrrdFprintFieldInfo())
105
      --> nrrd/keyvalue.c/_nrrdWriteEscaped()
106
        which is invoked to escape ",
107
        and (NOTE!) to convert all other whitespace to ' '
108
Same concern above about characters that when written would generate a
109
bad NRRD file, but which are not documented as escape-able in label or unit
110
111
** space units: A "-delimited string per axis of *world-space* (NOT
112
the same a per-axis field, like units).  Format is sadly silent on issue of
113
escaping for these; so we might as well treat them like labels & units
114
units. On input:
115
  nrrd/formatNrrd.c/_nrrdFormatNRRD_read()
116
  --> nrrd/parseNrrd.c/_nrrdReadNrrdParse_space_units
117
      --> nrrd/parseNrrd.c/_nrrdGetQuotedString()
118
          which does the work of unescaping \"
119
On output:
120
  nrrd/formatNrrd.c/_nrrdFormatNRRD_write()
121
  --> nrrd/write.c/_nrrdSprintFieldInfo()  (maybe via _nrrdFprintFieldInfo())
122
      --> nrrd/keyvalue.c/_nrrdWriteEscaped()
123
        which is invoked to escape ",
124
        and (NOTE!) to convert all other whitespace to ' '
125
126
** sample units: like content and comments, not a quoted string. On input:
127
  nrrd/formatNrrd.c/_nrrdFormatNRRD_read()
128
  --> nrrd/parseNrrd.c/_nrrdReadNrrdParse_sample_units()
129
      which does nothing except a strdup
130
On output:
131
  nrrd/formatNrrd.c/_nrrdFormatNRRD_write()
132
  --> nrrd/write.c/_nrrdSprintFieldInfo()  (maybe via _nrrdFprintFieldInfo())
133
      --> air/string.c/airOneLinify()
134
135
****
136
***/
137
138
#define MAGIC "NRRD"
139
#define MAGIC0 "NRRD00.01"
140
#define MAGIC1 "NRRD0001"
141
#define MAGIC2 "NRRD0002"
142
#define MAGIC3 "NRRD0003"
143
#define MAGIC4 "NRRD0004"
144
#define MAGIC5 "NRRD0005"
145
#define MAGIC6 "NRRD0006"
146
147
const char *
148
_nrrdFormatURLLine0 = "Complete NRRD file format specification at:";
149
const char *
150
_nrrdFormatURLLine1 = "http://teem.sourceforge.net/nrrd/format.html";
151
152
void
153
nrrdIoStateDataFileIterBegin(NrrdIoState *nio) {
154
155
68
  nio->dataFNIndex = 0;
156
34
  return;
157
}
158
159
/* this macro suggested by Bryan Worthen */
160
/* if str = '-', strcmp() is 0, && short circuits, return false
161
** else str != '-'
162
** if str[1] = ':', its probably a windows full path, != is 0, return false
163
** else str[1] != ':'
164
** if str[0] = '/', its a normal full path, return false
165
*/
166
#define _NEED_PATH(str) (strcmp("-", (str)) \
167
                         && ':' != (str)[1] \
168
                         && '/' != (str)[0])
169
170
/*
171
** this is responsible for the header-relative path processing
172
**
173
** NOTE: if the filename is "-", then because it does not start with '/',
174
** it would normally be prefixed by nio->path, so it needs special handling
175
**
176
** NOTE: this should work okay with nio->headerStringRead, I think ...
177
*/
178
int
179
nrrdIoStateDataFileIterNext(FILE **fileP, NrrdIoState *nio, int reading) {
180
  static const char me[]="nrrdIoStateDataFileIterNext";
181
  char *fname=NULL;
182
  int ii, needPath;
183
  unsigned int num, fi;
184
  size_t maxl;
185
  airArray *mop;
186
187
136
  mop = airMopNew();
188
68
  airMopAdd(mop, (void*)fileP, (airMopper)airSetNull, airMopOnError);
189
190
68
  if (!fileP) {
191
    biffAddf(NRRD, "%s: got NULL pointer", me);
192
    airMopError(mop); return 1;
193
  }
194
68
  if (!_nrrdDataFNNumber(nio)) {
195
    biffAddf(NRRD, "%s: there appear to be zero datafiles!", me);
196
    airMopError(mop); return 1;
197
  }
198
199
68
  if (nio->dataFNIndex >= _nrrdDataFNNumber(nio)) {
200
    /* there is no next data file, but we don't make that an error
201
       (though as of Tue Oct  2 22:53:14 CDT 2012, GLK can't remember
202
       why this condition would ever occur) */
203
34
    nio->dataFNIndex = _nrrdDataFNNumber(nio);
204
34
    airMopOkay(mop);
205
34
    *fileP = NULL;
206
34
    return 0;
207
  }
208
209
  /* HEY: some of this error checking is done far more often than needed */
210

68
  if (nio->dataFNFormat || nio->dataFNArr->len) {
211
    needPath = AIR_FALSE;
212
    maxl = 0;
213
6
    if (nio->dataFNFormat) {
214
      needPath = _NEED_PATH(nio->dataFNFormat);
215
      /* assuming 10-digit integers is plenty big */
216
      maxl = 10 + strlen(nio->dataFNFormat);
217
    } else {
218
24
      for (fi=0; fi<nio->dataFNArr->len; fi++) {
219

24
        needPath |= _NEED_PATH(nio->dataFN[fi]);
220
18
        maxl = AIR_MAX(maxl, strlen(nio->dataFN[fi]));
221
      }
222
    }
223

12
    if (needPath && !airStrlen(nio->path)) {
224
      biffAddf(NRRD, "%s: need nio->path for header-relative datafiles", me);
225
      airMopError(mop); return 1;
226
    }
227
6
    fname = (char*)malloc(airStrlen(nio->path) + strlen("/") + maxl + 1);
228
6
    if (!fname) {
229
      biffAddf(NRRD, "%s: couldn't allocate filename buffer", me);
230
      airMopError(mop); return 1;
231
    }
232
6
    airMopAdd(mop, fname, airFree, airMopAlways);
233
6
  }
234
235
34
  if (nio->dataFNFormat) {
236
    /* ---------------------------------------------------------- */
237
    /* --------- base.%d <min> <max> <step> [<dim>] ------------- */
238
    /* ---------------------------------------------------------- */
239
    num = 0;
240
    for (ii = nio->dataFNMin;
241
         ((nio->dataFNStep > 0 && ii <= nio->dataFNMax)
242
          || (nio->dataFNStep < 0 && ii >= nio->dataFNMax));
243
         ii += nio->dataFNStep) {
244
      if (num == nio->dataFNIndex) {
245
        break;
246
      }
247
      num += 1;
248
    }
249
    if (_NEED_PATH(nio->dataFNFormat)) {
250
      strcpy(fname, nio->path);
251
      strcat(fname, "/");
252
      sprintf(fname + strlen(nio->path) + strlen("/"), nio->dataFNFormat, ii);
253
    } else {
254
      sprintf(fname, nio->dataFNFormat, ii);
255
    }
256
34
  } else if (nio->dataFNArr->len) {
257
    /* ---------------------------------------------------------- */
258
    /* ------------------- LIST or single ----------------------- */
259
    /* ---------------------------------------------------------- */
260

18
    if (_NEED_PATH(nio->dataFN[nio->dataFNIndex])) {
261
6
      sprintf(fname, "%s/%s", nio->path, nio->dataFN[nio->dataFNIndex]);
262
6
    } else {
263
      strcpy(fname, nio->dataFN[nio->dataFNIndex]);
264
    }
265
  }
266
  /* else data file is attached */
267
268

68
  if (nio->dataFNFormat || nio->dataFNArr->len) {
269
6
    *fileP = airFopen(fname, reading ? stdin : stdout, reading ? "rb" : "wb");
270
6
    if (!(*fileP)) {
271
      biffAddf(NRRD, "%s: couldn't open \"%s\" (data file %u of %u) for %s",
272
               me, fname, nio->dataFNIndex+1, _nrrdDataFNNumber(nio),
273
               reading ? "reading" : "writing");
274
      airMopError(mop); return 1;
275
    }
276
  } else {
277
    /* data file is attached */
278
28
    if (nio->headerStringRead) {
279
      /* except we were never reading from a file to begin with, but this
280
         isn't an error */
281
      *fileP = NULL;
282
    } else {
283
28
      *fileP = nio->headerFile;
284
    }
285
  }
286
287
34
  nio->dataFNIndex++;
288
34
  airMopOkay(mop);
289
34
  return 0;
290
68
}
291
292
/*
293
** we try to use the oldest format that will hold the nrrd
294
*/
295
int
296
_nrrdFormatNRRD_whichVersion(const Nrrd *nrrd, NrrdIoState *nio) {
297
  int ret;
298
299
33
  if (nrrdEncodingZRL == nio->encoding
300
22
      || nrrdSpaceRightUp == nrrd->space
301
22
      || nrrdSpaceRightDown == nrrd->space) {
302
    ret = 6;
303
11
  } else if (_nrrdFieldInteresting(nrrd, nio, nrrdField_measurement_frame)) {
304
    ret = 5;
305
13
  } else if (_nrrdFieldInteresting(nrrd, nio, nrrdField_thicknesses)
306
6
             || _nrrdFieldInteresting(nrrd, nio, nrrdField_space)
307
6
             || _nrrdFieldInteresting(nrrd, nio, nrrdField_space_dimension)
308
5
             || _nrrdFieldInteresting(nrrd, nio, nrrdField_sample_units)
309

6
             || airStrlen(nio->dataFNFormat) || nio->dataFNArr->len > 1) {
310
    ret = 4;
311
3
  } else if (_nrrdFieldInteresting(nrrd, nio, nrrdField_kinds)) {
312
    ret = 3;
313
2
  } else if (nrrdKeyValueSize(nrrd)) {
314
    ret = 2;
315
  } else {
316
    ret = 1;
317
  }
318
11
  return ret;
319
}
320
321
static int
322
_nrrdFormatNRRD_available(void) {
323
324
48
  return AIR_TRUE;
325
}
326
327
static int
328
_nrrdFormatNRRD_nameLooksLike(const char *filename) {
329
330
36
  return (airEndsWith(filename, NRRD_EXT_NRRD)
331
25
          || airEndsWith(filename, NRRD_EXT_NHDR));
332
}
333
334
static int
335
_nrrdFormatNRRD_fitsInto(const Nrrd *nrrd, const NrrdEncoding *encoding,
336
                         int useBiff) {
337
  static const char me[]="_nrrdFormatNRRD_fitsInto";
338
339
22
  if (!( nrrd && encoding )) {
340
    biffMaybeAddf(useBiff, NRRD, "%s: got NULL nrrd (%p) or encoding (%p)",
341
                  me, AIR_CVOIDP(nrrd), AIR_CVOIDP(encoding));
342
    return AIR_FALSE;
343
  }
344
345
  /* everything fits in a nrrd */
346
11
  return AIR_TRUE;
347
11
}
348
349
static int
350
_nrrdFormatNRRD_contentStartsLike(NrrdIoState *nio) {
351
352
144
  return (!strcmp(MAGIC0, nio->line)
353
96
          || !strcmp(MAGIC1, nio->line)
354
88
          || !strcmp(MAGIC2, nio->line)
355
80
          || !strcmp(MAGIC3, nio->line)
356
76
          || !strcmp(MAGIC4, nio->line)
357
66
          || !strcmp(MAGIC5, nio->line)
358
80
          || !strcmp(MAGIC6, nio->line)
359
          );
360
}
361
362
/*
363
** _nrrdHeaderCheck()
364
**
365
** minimal consistency checks on relationship between fields of nrrd,
366
** only to be used after the headers is parsed, and before the data is
367
** read, to make sure that information required for reading data is in
368
** fact known.
369
**
370
** NOTE: this is not the place to do the sort of checking done by
371
** nrrdCheck(), because it includes I/O-specific stuff
372
**
373
*/
374
int
375
_nrrdHeaderCheck(Nrrd *nrrd, NrrdIoState *nio, int checkSeen) {
376
  static const char me[]="_nrrdHeaderCheck";
377
  int i;
378
379
46
  if (checkSeen) {
380
1518
    for (i=1; i<=NRRD_FIELD_MAX; i++) {
381

828
      if (_nrrdFieldRequired[i] && !nio->seen[i]) {
382
        biffAddf(NRRD, "%s: didn't see required field: %s",
383
                 me, airEnumStr(nrrdField, i));
384
        return 1;
385
      }
386
    }
387
  }
388

23
  if (nrrdTypeBlock == nrrd->type && !nrrd->blockSize) {
389
    biffAddf(NRRD, "%s: type is %s, but missing field: %s", me,
390
             airEnumStr(nrrdType, nrrdTypeBlock),
391
             airEnumStr(nrrdField, nrrdField_block_size));
392
    return 1;
393
  }
394
23
  if (!nrrdElementSize(nrrd)) {
395
    biffAddf(NRRD, "%s: nrrd reports zero element size!", me);
396
    return 1;
397
  }
398
  /* _nrrdReadNrrdParse_sizes() checks axis[i].size, which completely
399
     determines the return of nrrdElementNumber() */
400
23
  if (airEndianUnknown == nio->endian
401
23
      && nio->encoding->endianMatters
402
      && 1 != nrrdElementSize(nrrd)) {
403
    biffAddf(NRRD, "%s: type (%s) and encoding (%s) require %s info", me,
404
             airEnumStr(nrrdType, nrrd->type),
405
             nio->encoding->name,
406
             airEnumStr(nrrdField, nrrdField_endian));
407
    return 1;
408
  }
409
410
  /* we don't really try to enforce consistency with the
411
     min/max/center/size information on each axis, other than the
412
     value checking done by the _nrrdReadNrrdParse_* functions,
413
     because we only really care that we know each axis size.  Past
414
     that, if the user messes it up, its not really our problem ... */
415
416
23
  return 0;
417
23
}
418
419
/*
420
** NOTE: currently, this will read, without complaints or errors,
421
** newer NRRD format features from older NRRD files (as indicated by
422
** magic), such as key/value pairs from a NRRD0001 file, even though
423
** strictly speaking these are violations of the format.
424
**
425
** NOTE: by giving a NULL "file", you can make this function basically
426
** do the work of reading in datafiles, without any header parsing
427
*/
428
static int
429
_nrrdFormatNRRD_read(FILE *file, Nrrd *nrrd, NrrdIoState *nio) {
430
  static const char me[]="_nrrdFormatNRRD_read";
431
  /* Dynamically allocated for space reasons. */
432
  /* MWC: These strlen usages look really unsafe. */
433
  int ret;
434
46
  unsigned int llen;
435
  size_t valsPerPiece;
436
  char *data;
437
23
  FILE *dataFile=NULL;
438
439
  /* record where the header is being read from for the sake of
440
     nrrdIoStateDataFileIterNext() */
441
23
  nio->headerFile = file;
442
443
  /* GLK forgets the context in which file might be reasonably NULL
444
     but on Fri Sep 23 09:48:41 EDT 2005 this was "if (file) { ..." */
445
  /* nio->headerStringRead is NULL whenever IO from string is not being done */
446

23
  if (file || nio->headerStringRead) {
447
23
    if (!_nrrdFormatNRRD_contentStartsLike(nio)) {
448
      biffAddf(NRRD, "%s: this doesn't look like a %s file", me,
449
               nrrdFormatNRRD->name);
450
      return 1;
451
    }
452
    /* parse all the header lines */
453
    do {
454
266
      nio->pos = 0;
455
266
      if (_nrrdOneLine(&llen, nio, file)) {
456
        biffAddf(NRRD, "%s: trouble getting line of header", me);
457
        return 1;
458
      }
459
266
      if (llen > 1) {
460
243
        ret = _nrrdReadNrrdParseField(nio, AIR_TRUE);
461
243
        if (!ret) {
462
          biffAddf(NRRD, "%s: trouble parsing NRRD field identifier from "
463
                   "in \"%s\"", me, nio->line);
464
          return 1;
465
        }
466
        /* comments and key/values are allowed multiple times */
467
243
        if (nio->seen[ret]
468
243
            && !(ret == nrrdField_comment || ret == nrrdField_keyvalue)) {
469
          biffAddf(NRRD, "%s: already set field %s", me,
470
                   airEnumStr(nrrdField, ret));
471
          return 1;
472
        }
473
243
        if (nrrdFieldInfoParse[ret](file, nrrd, nio, AIR_TRUE)) {
474
          biffAddf(NRRD, "%s: trouble parsing %s info |%s|", me,
475
                   airEnumStr(nrrdField, ret), nio->line + nio->pos);
476
          return 1;
477
        }
478
243
        nio->seen[ret] = AIR_TRUE;
479
243
      }
480
266
    } while (llen > 1);
481
    /* either
482
       0 == llen: we're at EOF (or end of nio->headerStringRead), or
483
       1 == llen: we just read the empty line separating header from data */
484
29
    if (0 == llen
485
29
        && !nio->headerStringRead
486
12
        && !nio->dataFNFormat
487
12
        && 0 == nio->dataFNArr->len) {
488
      /* we're at EOF, we're not reading from a string, but there's
489
         apparently no separate data file */
490
      biffAddf(NRRD, "%s: hit end of header, but no \"%s\" given", me,
491
               airEnumStr(nrrdField, nrrdField_data_file));
492
      return 1;
493
    }
494
  }
495
23
  if (_nrrdHeaderCheck(nrrd, nio, !!file)) {
496
    biffAddf(NRRD, "%s: %s", me,
497
             (llen ? "finished reading header, but there were problems"
498
              : "hit EOF before seeing a complete valid header"));
499
    return 1;
500
  }
501
502
  /* we seemed to have read in a valid header; now allocate the memory.
503
     For directIO-compatible allocation we need to get the first datafile */
504
23
  nrrdIoStateDataFileIterBegin(nio);
505
  /* NOTE: if nio->headerStringRead, this may set dataFile to NULL */
506
23
  if (nrrdIoStateDataFileIterNext(&dataFile, nio, AIR_TRUE)) {
507
    biffAddf(NRRD, "%s: couldn't open the first datafile", me);
508
    return 1;
509
  }
510
23
  if (nio->skipData) {
511
    nrrd->data = NULL;
512
    data = NULL;
513
  } else {
514
23
    if (_nrrdCalloc(nrrd, nio, dataFile)) {
515
      biffAddf(NRRD, "%s: couldn't allocate memory for data", me);
516
      return 1;
517
    }
518
23
    data = (char*)nrrd->data;
519
  }
520
521
  /* iterate through datafiles and read them in */
522
  /* NOTE: you have to open dataFile even in the case of skipData, because
523
     caller might have set keepNrrdDataFileOpen, in which case you need to
524
     do any line or byte skipping if it is specified */
525
23
  valsPerPiece = nrrdElementNumber(nrrd)/_nrrdDataFNNumber(nio);
526
69
  while (dataFile) {
527
    /* ---------------- skip, if need be */
528
23
    if (nrrdLineSkip(dataFile, nio)) {
529
      biffAddf(NRRD, "%s: couldn't skip lines", me);
530
      return 1;
531
    }
532
23
    if (!nio->encoding->isCompression) {
533
      /* bytes are skipped here for non-compression encodings, but are
534
         skipped within the decompressed stream for compression encodings */
535
23
      if (nio->dataFSkip) {
536
        /* this error checking is clearly done unnecessarily repeated,
537
           but it was logically the simplest place to add it */
538
        if (nio->byteSkip) {
539
          biffAddf(NRRD, "%s: using per-list-line skip, "
540
                   "but also set global byte skip %ld", me, nio->byteSkip);
541
          return 1;
542
        }
543
        /* wow, the meaning of nio->dataFNIndex is a little confusing */
544
        if (_nrrdByteSkipSkip(dataFile, nrrd, nio, nio->dataFSkip[nio->dataFNIndex-1])) {
545
          biffAddf(NRRD, "%s: couldn't skip %ld bytes on for list line %u",
546
                   me, nio->dataFSkip[nio->dataFNIndex-1], nio->dataFNIndex-1);
547
          return 1;
548
        }
549
      } else {
550
23
        if (nrrdByteSkip(dataFile, nrrd, nio)) {
551
          biffAddf(NRRD, "%s: couldn't skip bytes", me);
552
          return 1;
553
        }
554
      }
555
    }
556
    /* ---------------- read the data itself */
557
23
    if (2 <= nrrdStateVerboseIO) {
558
      fprintf(stderr, "(%s: reading %s data ... ", me, nio->encoding->name);
559
      fflush(stderr);
560
    }
561
23
    if (!nio->skipData) {
562
23
      if (nio->encoding->read(dataFile, data, valsPerPiece, nrrd, nio)) {
563
        if (2 <= nrrdStateVerboseIO) {
564
          fprintf(stderr, "error!\n");
565
        }
566
        biffAddf(NRRD, "%s:", me);
567
        return 1;
568
      }
569
    }
570
23
    if (2 <= nrrdStateVerboseIO) {
571
      fprintf(stderr, "done)\n");
572
    }
573
    /* ---------------- go to next data file */
574

23
    if (nio->keepNrrdDataFileOpen && _nrrdDataFNNumber(nio) == 1) {
575
      nio->dataFile = dataFile;
576
    } else {
577
23
      if (dataFile != nio->headerFile) {
578
6
        dataFile = airFclose(dataFile);
579
6
      }
580
    }
581
23
    data += valsPerPiece*nrrdElementSize(nrrd);
582
23
    if (nrrdIoStateDataFileIterNext(&dataFile, nio, AIR_TRUE)) {
583
      biffAddf(NRRD, "%s: couldn't get the next datafile", me);
584
      return 1;
585
    }
586
  }
587
588

46
  if (airEndianUnknown != nio->endian && nrrd->data) {
589
    /* we positively know the endianness of data just read */
590
46
    if (1 < nrrdElementSize(nrrd)
591
46
        && nio->encoding->endianMatters
592
46
        && nio->endian != airMyEndian()) {
593
      /* endianness exposed in encoding, and its wrong */
594
      if (2 <= nrrdStateVerboseIO) {
595
        fprintf(stderr, "(%s: fixing endianness ... ", me);
596
        fflush(stderr);
597
      }
598
      nrrdSwapEndian(nrrd);
599
      if (2 <= nrrdStateVerboseIO) {
600
        fprintf(stderr, "done)\n");
601
        fflush(stderr);
602
      }
603
    }
604
  }
605
606
23
  return 0;
607
23
}
608
609
static int
610
_nrrdFormatNRRD_write(FILE *file, const Nrrd *nrrd, NrrdIoState *nio) {
611
  static const char me[]="_nrrdFormatNRRD_write";
612
22
  char strbuf[AIR_STRLEN_MED], *strptr, *tmp;
613
  int ii;
614
  unsigned int jj;
615
  airArray *mop;
616
11
  FILE *dataFile=NULL;
617
  size_t valsPerPiece;
618
  char *data;
619
620
11
  mop = airMopNew();
621
622
11
  if (!(file
623
11
        || nio->headerStringWrite
624
        || nio->learningHeaderStrlen)) {
625
    biffAddf(NRRD, "%s: have no file or string to write to, nor are "
626
             "learning header string length", me);
627
    airMopError(mop); return 1;
628
  }
629

11
  if (nrrdTypeBlock == nrrd->type && nrrdEncodingAscii == nio->encoding) {
630
    biffAddf(NRRD, "%s: can't write nrrd type %s with %s encoding", me,
631
             airEnumStr(nrrdType, nrrdTypeBlock),
632
             nrrdEncodingAscii->name);
633
    airMopError(mop); return 1;
634
  }
635
636
  /* record where the header is being written to for the sake of
637
     nrrdIoStateDataFileIterNext(). This may be NULL if
638
     nio->headerStringWrite is non-NULL */
639
11
  nio->headerFile = file;
640
641
  /* we have to make sure that the data filename information is set
642
     (if needed), so that it can be printed by _nrrdFprintFieldInfo */
643
11
  if (nio->detachedHeader
644
11
      && !nio->dataFNFormat
645
      && 0 == nio->dataFNArr->len) {
646
    /* NOTE: this means someone requested a detached header, but we
647
       don't already have implicit (via dataFNFormat) or explicit
648
       (via dataFN[]) information about the data file */
649
    /* NOTE: whether or not nio->skipData, we have to contrive a filename to
650
       say in the "data file" field, which is stored in nio->dataFN[0],
651
       because the data filename will be "interesting", according to
652
       _nrrdFieldInteresting() */
653
    /* NOTE: Fri Feb  4 01:42:20 EST 2005 the way this is now set up, having
654
       a name in dataFN[0] will trump the name implied by nio->{path,base},
655
       which is a useful way for the user to explicitly set the output
656
       data filename (as with unu make -od) */
657
    if (!( !!airStrlen(nio->path) && !!airStrlen(nio->base) )) {
658
      biffAddf(NRRD, "%s: can't create data file name: nio's "
659
               "path and base empty", me);
660
      airMopError(mop); return 1;
661
    }
662
    tmp = (char*)malloc(strlen(nio->base)
663
                        + strlen(".")
664
                        + strlen(nio->encoding->suffix) + 1);
665
    if (!tmp) {
666
      biffAddf(NRRD, "%s: couldn't allocate data filename", me);
667
      airMopError(mop); return 1;
668
    }
669
    airMopAdd(mop, tmp, airFree, airMopOnError);
670
    sprintf(tmp, "%s.%s", nio->base, nio->encoding->suffix);
671
    jj = airArrayLenIncr(nio->dataFNArr, 1);
672
    if (!nio->dataFNArr->data) {
673
      biffAddf(NRRD, "%s: can't increase dataFNArr storage", me);
674
      airMopError(mop); return 1;
675
    }
676
    nio->dataFN[jj] = tmp;
677
  }
678
679
  /* the magic is in fact the first thing to be written */
680
11
  if (file) {
681
11
    fprintf(file, "%s%04d\n", MAGIC, _nrrdFormatNRRD_whichVersion(nrrd, nio));
682
11
  } else if (nio->headerStringWrite) {
683
    sprintf(nio->headerStringWrite, "%s%04d\n",
684
            MAGIC, _nrrdFormatNRRD_whichVersion(nrrd, nio));
685
  } else {
686
    nio->headerStrlen = AIR_CAST(unsigned int, strlen(MAGIC) + strlen("0000")) + 1;
687
  }
688
689
  /* write the advertisement about where to get the file format */
690
11
  if (!nio->skipFormatURL) {
691
11
    if (file) {
692
11
      fprintf(file, "# %s\n", _nrrdFormatURLLine0);
693
11
      fprintf(file, "# %s\n", _nrrdFormatURLLine1);
694

11
    } else if (nio->headerStringWrite) {
695
      sprintf(strbuf, "# %s\n", _nrrdFormatURLLine0);
696
      strcat(nio->headerStringWrite, strbuf);
697
      sprintf(strbuf, "# %s\n", _nrrdFormatURLLine1);
698
      strcat(nio->headerStringWrite, strbuf);
699
    } else {
700
      nio->headerStrlen += sprintf(strbuf, "# %s\n", _nrrdFormatURLLine0);
701
      nio->headerStrlen += sprintf(strbuf, "# %s\n", _nrrdFormatURLLine1);
702
    }
703
  }
704
705
  /* this is where the majority of the header printing happens */
706
726
  for (ii=1; ii<=NRRD_FIELD_MAX; ii++) {
707
352
    if (_nrrdFieldInteresting(nrrd, nio, ii)) {
708
116
      if (file) {
709
116
        _nrrdFprintFieldInfo(file, "", nrrd, nio, ii, AIR_FALSE);
710

116
      } else if (nio->headerStringWrite) {
711
        _nrrdSprintFieldInfo(&strptr, "", nrrd, nio, ii, AIR_FALSE);
712
        if (strptr) {
713
          strcat(nio->headerStringWrite, strptr);
714
          strcat(nio->headerStringWrite, "\n");
715
          free(strptr);
716
          strptr = NULL;
717
        }
718
      } else {
719
        _nrrdSprintFieldInfo(&strptr, "", nrrd, nio, ii, AIR_FALSE);
720
        if (strptr) {
721
          nio->headerStrlen += AIR_CAST(unsigned int, strlen(strptr));
722
          nio->headerStrlen += AIR_CAST(unsigned int, strlen("\n"));
723
          free(strptr);
724
          strptr = NULL;
725
        }
726
      }
727
    }
728
  }
729
730
  /* comments and key/value pairs handled differently */
731
28
  for (jj=0; jj<nrrd->cmtArr->len; jj++) {
732
    char *strtmp;
733
3
    strtmp = airOneLinify(airStrdup(nrrd->cmt[jj]));
734
3
    if (file) {
735
3
      fprintf(file, "%c %s\n", NRRD_COMMENT_CHAR, strtmp);
736

3
    } else if (nio->headerStringWrite) {
737
      strptr = (char*)malloc(1 + strlen(" ")
738
                             + strlen(strtmp) + strlen("\n") + 1);
739
      sprintf(strptr, "%c %s\n", NRRD_COMMENT_CHAR, strtmp);
740
      strcat(nio->headerStringWrite, strptr);
741
      free(strptr);
742
      strptr = NULL;
743
    } else {
744
      nio->headerStrlen += (1 + AIR_CAST(unsigned int, strlen(" ")
745
                                         + strlen(strtmp)
746
                                         + strlen("\n")) + 1);
747
    }
748
3
    airFree(strtmp);
749
  }
750
28
  for (jj=0; jj<nrrd->kvpArr->len; jj++) {
751
3
    if (file) {
752
3
      _nrrdKeyValueWrite(file, NULL,
753
3
                         NULL, nrrd->kvp[0 + 2*jj], nrrd->kvp[1 + 2*jj]);
754

3
    } else if (nio->headerStringWrite) {
755
      _nrrdKeyValueWrite(NULL, &strptr,
756
                         NULL, nrrd->kvp[0 + 2*jj], nrrd->kvp[1 + 2*jj]);
757
      if (strptr) {
758
        strcat(nio->headerStringWrite, strptr);
759
        free(strptr);
760
        strptr = NULL;
761
      }
762
    } else {
763
      _nrrdKeyValueWrite(NULL, &strptr,
764
                         NULL, nrrd->kvp[0 + 2*jj], nrrd->kvp[1 + 2*jj]);
765
      if (strptr) {
766
        nio->headerStrlen += AIR_CAST(unsigned int, strlen(strptr));
767
        free(strptr);
768
        strptr = NULL;
769
      }
770
    }
771
  }
772
773
11
  if (file) {
774

22
    if (!( nio->detachedHeader || _nrrdDataFNNumber(nio) > 1 )) {
775
11
      fprintf(file, "\n");
776
11
    }
777
  }
778
779

22
  if (file && !nio->skipData) {
780
11
    nrrdIoStateDataFileIterBegin(nio);
781
11
    if (nrrdIoStateDataFileIterNext(&dataFile, nio, AIR_FALSE)) {
782
      biffAddf(NRRD, "%s: couldn't write the first datafile", me);
783
      airMopError(mop); return 1;
784
    }
785
786
11
    valsPerPiece = nrrdElementNumber(nrrd)/_nrrdDataFNNumber(nio);
787
11
    data = (char*)nrrd->data;
788
11
    do {
789
      /* ---------------- write data */
790
11
      if (2 <= nrrdStateVerboseIO) {
791
        fprintf(stderr, "(%s: writing %s data ", me, nio->encoding->name);
792
        fflush(stderr);
793
      }
794

22
      if (nio->encoding->write(dataFile, data, valsPerPiece, nrrd, nio)) {
795
11
        if (2 <= nrrdStateVerboseIO) {
796
          fprintf(stderr, "error!\n");
797
        }
798
        biffAddf(NRRD, "%s: couldn't write %s data", me, nio->encoding->name);
799
        airMopError(mop); return 1;
800
      }
801
11
      if (2 <= nrrdStateVerboseIO) {
802
        fprintf(stderr, "done)\n");
803
      }
804
      /* ---------------- go to next data file */
805
11
      if (dataFile != nio->headerFile) {
806
        dataFile = airFclose(dataFile);
807
      }
808
11
      data += valsPerPiece*nrrdElementSize(nrrd);
809
11
      if (nrrdIoStateDataFileIterNext(&dataFile, nio, AIR_TRUE)) {
810
        biffAddf(NRRD, "%s: couldn't get the next datafile", me);
811
        airMopError(mop); return 1;
812
      }
813
11
    } while (dataFile);
814
  }
815
816
11
  airMopOkay(mop);
817
11
  return 0;
818
11
}
819
820
const NrrdFormat
821
_nrrdFormatNRRD = {
822
  "NRRD",
823
  AIR_FALSE,  /* isImage */
824
  AIR_TRUE,   /* readable */
825
  AIR_TRUE,   /* usesDIO */
826
  _nrrdFormatNRRD_available,
827
  _nrrdFormatNRRD_nameLooksLike,
828
  _nrrdFormatNRRD_fitsInto,
829
  _nrrdFormatNRRD_contentStartsLike,
830
  _nrrdFormatNRRD_read,
831
  _nrrdFormatNRRD_write
832
};
833
834
const NrrdFormat *const
835
nrrdFormatNRRD = &_nrrdFormatNRRD;