1 #include "niml_private.h"
2 
3 /**********************************************************************/
4 /******* Functions to read and write data and group elements. *********/
5 /**********************************************************************/
6 
7 static int scan_for_angles( NI_stream_type *, int ) ;
8 
9 #define clear_buffer(ns) ( (ns)->nbuf = (ns)->npos = 0 )
10 
11 /*--------------------------------------------------------------------*/
12 /*! Check if header_stuff marks this NIML element as a group.
13 ----------------------------------------------------------------------*/
14 
header_stuff_is_group(header_stuff * hs)15 static int header_stuff_is_group( header_stuff *hs )  /* 24 Feb 2005 */
16 {
17    char *atr ;
18    if( hs == NULL ) return 0 ;
19    if( strcmp(hs->name,"ni_group") == 0 ) return 1 ;
20    atr = get_header_attribute( hs , "ni_form" ) ;
21    if( atr != NULL && strcmp(atr,"ni_group") == 0 ) return 1 ;
22    return 0 ;
23 }
24 
25 /*--------------------------------------------------------------------*/
26 /*! Check if header_stuff marks NIML element as a processing instruction.
27 ----------------------------------------------------------------------*/
28 
header_stuff_is_procins(header_stuff * hs)29 static int header_stuff_is_procins( header_stuff *hs )
30 {
31    if( hs == NULL ) return 0 ;
32    if( hs->name != NULL && hs->name[0] == '?' ) return 1 ;
33    return 0 ;
34 }
35 
36 /*--------------------------------------------------------------------*/
37 /*! Write a simple processing instruction to the stream:
38     - "<?str ?>\n" will be written
39     - Return value is the number of bytes written
40     - Return 0 means that the stream wasn't ready to write
41     - Return -1 means an error happened, and nothing was written
42     - 17 Mar 2005 - RWCox
43 ----------------------------------------------------------------------*/
44 
NI_write_procins(NI_stream_type * ns,char * str)45 int NI_write_procins( NI_stream_type *ns , char *str )
46 {
47    char *buf ; int jj ;
48 
49    /* check inputs for good-ositifulness */
50 
51    if( !NI_stream_writeable(ns)             ) return -1 ;  /* stupid user */
52    if( str == NULL || !IS_STRING_CHAR(*str) ) return -1 ;
53 
54    /* check if stream is ready to take data */
55 
56    if( ns->bad ){                       /* socket that hasn't connected yet */
57      jj = NI_stream_goodcheck(ns,666) ; /* try to connect it */
58      if( jj < 1 ) return jj ;           /* 0 is nothing yet, -1 is death */
59    } else {                             /* check if good ns has gone bad */
60      jj = NI_stream_writecheck(ns,666) ;
61      if( jj < 0 ) return jj ;
62    }
63 
64    /* write the processing instruction: "<?str ?>\n" */
65 
66    buf = (char *)malloc(strlen(str)+16) ;
67    sprintf( buf , "<?%s ?>\n" , str ) ;
68    jj = NI_stream_writestring( ns , buf ) ;
69 
70    free((void *)buf) ; return jj ;
71 }
72 
73 
74 /*--------------------------------------------------------------------*/
75 
76 static int read_header_only = 0 ;
NI_set_read_header_only(int r)77 void NI_set_read_header_only( int r ){ read_header_only=r ; } /* 23 Mar 2003 */
NI_get_read_header_only(void)78 int NI_get_read_header_only( void ){ return(read_header_only); }/*ZSS Feb 2012 */
79 
80 static int skip_procins = 0 ;
NI_skip_procins(int r)81 void NI_skip_procins( int r ){ skip_procins = r ; }       /* 03 Jun 2005 */
82 
83 /*--------------------------------------------------------------------*/
84 /*! Read only the header part of the next element.
85 ----------------------------------------------------------------------*/
86 
NI_read_element_header(NI_stream_type * ns,int msec)87 void * NI_read_element_header( NI_stream_type *ns , int msec )
88 {
89    void *nini ;
90    read_header_only = 1 ;
91    nini = NI_read_element( ns , msec ) ;
92    read_header_only = 0 ;
93    return nini ;
94 }
95 
96 /*--------------------------------------------------------------------*/
97 /*! Read an element (maybe a group) from the stream, waiting up to
98     msec milliseconds for the header to appear.  (After that, this
99     function may wait a long time for the rest of the element to
100     appear, unless the data stream comes to a premature end.)
101 
102    Return is NULL if nothing can be read at this time.  Otherwise,
103    use NI_element_type(return value) to determine if the element
104    read is a data element or a group element.
105 
106    Note that a header that is longer than ns->bufsize will
107    never be read properly, since we must have the entire header in
108    the buffer before processing it.  This should only be a problem
109    for deranged users (e.g., Ziad).  If such a vast header is encountered,
110    it will be flushed.
111 
112    If header start '<' and stop '>' are encountered, then this
113    function will read data until it can create an element, or until
114    the data stream is bad (i.e., the file ends, or the socket closes).
115 
116    If NULL is returned, that can be because there is no data to
117    read even in the buffer, or because the input data stream has gone
118    bad (i.e., will return no more data ever).  To check for the latter
119    case, use NI_stream_readcheck().
120 
121    If a "<ni_do ... />" or "<?ni_do ... ?>" element is encountered,
122    it will not be returned to the caller.  Instead, the actions it
123    orders will be carried out in function NI_do(), and the function
124    will loop back to find some other input.
125 ----------------------------------------------------------------------*/
126 
NI_read_element(NI_stream_type * ns,int msec)127 void * NI_read_element( NI_stream_type *ns , int msec )
128 {
129    int ii,nn,nhs , num_restart ;
130    char *cstart , *cstop ;
131    header_stuff *hs ;
132    int start_time=NI_clock_time() , mleft ;
133 
134    if( ns == NULL || ns->bad == MARKED_FOR_DEATH || ns->buf == NULL )
135      return NULL ;  /* bad input stream */
136 
137 #ifdef NIML_DEBUG
138 NI_dpr("ENTER NI_read_element\n") ;
139 #endif
140 
141    if( msec < 0 ) msec = 999999999 ;  /* a long time (11+ days) */
142 
143    /* if we have a socket that hasn't connected,
144       then see if it can connect now            */
145 
146    if( ns->bad ){
147      nn = NI_stream_goodcheck( ns , msec ) ;
148      if( nn < 1 ) return NULL ;              /* didn't connect */
149    }
150 
151    /*-- Try to find the element header --*/
152 
153    num_restart = 0 ;
154 HeadRestart:                            /* loop back here to retry */
155    num_restart++ ;
156    mleft = msec - (NI_clock_time()-start_time) ;      /* time left */
157    if( num_restart > 1 && mleft <= 0 ) return NULL ;  /* don't allow too many loops */
158 
159 #ifdef NIML_DEBUG
160 NI_dpr("NI_read_element: HeadRestart scan_for_angles; num_restart=%d\n" ,
161                num_restart ) ;
162 #endif
163 
164    nn = scan_for_angles( ns , 0 ) ;     /* look for '<stuff>' */
165 
166    /* didn't find it */
167 
168    if( nn < 0 ){
169 #ifdef NIML_DEBUG
170 NI_dpr("NI_read_element: scan_for_angles() returns %d",nn) ;
171 #endif
172      if( NI_stream_readcheck(ns,1) < 0 ) return NULL ;   /* connection lost */
173 #ifdef NIML_DEBUG
174 NI_dpr("                 trying again") ;
175 #endif
176      NI_sleep(2); goto HeadRestart;                      /* try again */
177    }
178 
179 #ifdef NIML_DEBUG
180 NI_dpr("NI_read_element: found '<'\n") ;
181 #endif
182 
183    /* ns->buf[ns->npos] = opening '<' ; ns->buf[nn-1] = closing '>' */
184 
185    /* see if we found '<>', which is meaningless,
186       or a trailer '</stuff>', which is illegal here */
187 
188    if( nn - ns->npos <= 2 || ns->buf[ns->npos+1] == '/' ){
189       ns->npos = nn; NI_reset_buffer(ns); /* toss the '<..>', try again */
190 #ifdef NIML_DEBUG
191 NI_dpr("NI_read_element: illegal header found? skipping\n") ;
192 #endif
193       goto HeadRestart ;
194    }
195 
196    /*----- Parse the header data and prepare to make an element! -----*/
197 
198 #ifdef NIML_DEBUG
199 NI_dpr("NI_read_element: parsing putative header\n") ;
200 #endif
201 
202    hs = parse_header_stuff( nn - ns->npos , ns->buf + ns->npos , &nhs ) ;
203 
204    if( hs == NULL ){  /* something bad happened there */
205      fprintf(stderr,"NI_read_element: bad element header found!\n") ;
206      ns->npos = nn; NI_reset_buffer(ns); /* toss the '<..>', try again */
207      goto HeadRestart ;
208    }
209 
210    /*----- If here, have parsed a header (and will not HeadRestart).
211            First, expunge the data bytes that were consumed to make
212            the header; that is, we can then start reading data from
213            ns->buf[ns->npos] .. ns->buf[ns->nbuf-1]                 --*/
214 
215    ns->npos = nn ;
216 
217 #ifdef NIML_DEBUG
218 NI_dpr("NI_read_element: header parsed successfully\n") ;
219 #endif
220 
221    /*--------------- Now make an element of some kind ---------------*/
222 
223    if( header_stuff_is_procins(hs) ){       /*--- a processing instruction ---*/
224 
225      NI_procins *npi ;
226 
227      if( strcmp(hs->name,"?ni_do") == 0 ){  /* 19 Apr 2005: special case! */
228        NI_element *nel ;
229        nel = make_empty_data_element( hs ) ;         /* temporary element */
230        destroy_header_stuff( hs ) ;
231        NI_do( ns , nel ) ;                        /* do the stuff it says */
232        NI_free_element( nel ) ;                        /* then destroy it */
233        if( ns->bad == MARKED_FOR_DEATH || ns->buf == NULL ) return NULL ;
234        num_restart = 0 ; goto HeadRestart ;
235      }
236 
237      /* 03 Jun 2005: if ordered to skip these things, do so */
238 
239      if( skip_procins ){
240        destroy_header_stuff( hs ) ; num_restart = 0 ; goto HeadRestart ;
241      }
242 
243      /* normal case: make a procins element and give it to the caller */
244 
245      npi       = NI_malloc(NI_procins,sizeof(NI_procins)) ;
246      npi->type = NI_PROCINS_TYPE ;
247      npi->name = NI_strdup( hs->name + 1 ) ; /* skip the '?' */
248 
249      npi->attr_num = hs->nattr ;
250      if( npi->attr_num > 0 ){
251        npi->attr_lhs = hs->lhs ; hs->lhs = NULL ;
252        npi->attr_rhs = hs->rhs ; hs->rhs = NULL ;
253      } else {
254        npi->attr_lhs = npi->attr_rhs = NULL ;
255      }
256 
257      destroy_header_stuff( hs ) ;
258 
259      return npi ;
260 
261    } /*--- end of reading a processing instruction ---*/
262 
263    else if( header_stuff_is_group(hs) ){           /*---- a group element ----*/
264 
265       NI_group *ngr ;
266       void *nini ;
267       int   empty=hs->empty ;
268 
269       read_header_only = 0 ;         /* 23 Mar 2003 */
270 
271       start_time = NI_clock_time() ; /* allow up to 10 sec for next */
272       msec       = 9999 ;            /* element to appear, before giving up */
273 
274       ngr = make_empty_group_element( hs ) ;  /* copies name and attributes */
275       destroy_header_stuff( hs ) ;
276       if( empty ) return ngr ;  /* 03 Jun 2002: empty group is legal */
277 
278       /* we now have to read the elements within the group */
279 
280       num_restart = 0 ;
281       while(1){           /* loop to find an element */
282 
283 #ifdef NIML_DEBUG
284 NI_dpr("NI_read_element: ni_group scan_for_angles; num_restart=%d\n",
285                num_restart ) ;
286 #endif
287 
288          nn = scan_for_angles( ns , 10 ) ;  /* find header/trailer '<...>' */
289 
290          mleft = msec - (NI_clock_time()-start_time) ;
291          if( mleft < 0 ) mleft = 0 ;
292 
293          if( nn <= 0 ){  /* didn't find it */
294            if( NI_stream_readcheck(ns,0) < 0 ) break ;  /* real bad */
295            if( num_restart > 1 && mleft == 0 ) break ;  /* time's up */
296            num_restart++ ;
297            continue ;        /* try again (but not forever) */
298          }
299 
300          /* check if we found a trailer element '</stuff>' */
301 
302          if( ns->buf[ns->npos+1] == '/' ){  /* trailer */
303            ns->npos = nn ;                 /* so end the group */
304            break ;
305          }
306 
307          /* not a trailer, so try to make an element out of it */
308 
309          nini = NI_read_element( ns , mleft ) ;   /* recursion! */
310          if( nini != NULL ){
311             NI_add_to_group( ngr , nini ) ;  /* this is good */
312             num_restart = 0 ;
313             start_time = NI_clock_time() ;   /* restart the wait clock */
314          } else {                            /* this is bad */
315             if( NI_stream_readcheck(ns,0) < 0 ) break ;    /* real bad */
316             mleft = msec - (NI_clock_time()-start_time) ;
317             if( num_restart > 1 && mleft <= 0 ) break ;    /* time's up */
318             num_restart++ ;
319          }
320       }
321 
322       /* and we are done */
323 
324       return ngr ;
325 
326    } /* end of reading group element */
327 
328    else {      /*------------------------ a data element ---------------------*/
329 
330       NI_element *nel ;
331       int form, swap, nbrow , row,col ;
332 
333       nel = make_empty_data_element( hs ) ;
334       destroy_header_stuff( hs ) ;
335 
336       /*-- check if this is an empty element --*/
337       if( nel           == NULL ||     /* nel == NULL should never happen. */
338           nel->vec_rank == 0    ||     /* These other cases are indication */
339           nel->vec_num  == 0    ||     /* that this is an 'empty' element. */
340           nel->vec_typ  == NULL ||     /* ==> The header is all there is.  */
341           nel->vec      == NULL ||
342           nel->name[0]  == '!'  ||     /* Stupid XML declaration */
343           read_header_only        ){
344 
345 #ifdef NIML_DEBUG
346 NI_dpr("NI_read_element: returning empty element\n") ;
347 #endif
348 
349         /*-- 23 Aug 2002: do something, instead of returning data? --*/
350 
351         if( nel != NULL && strcmp(nel->name,"ni_do") == 0 ){
352           NI_do( ns , nel ) ;
353           NI_free_element( nel ) ;
354           if( ns->bad == MARKED_FOR_DEATH || ns->buf == NULL ) return NULL ;
355           num_restart = 0 ; goto HeadRestart ;
356         }
357 
358         if( read_header_only && nel->vec != NULL ){
359           for( ii=0 ; ii < nel->vec_num ; ii++ ) NI_free(nel->vec[ii]) ;
360           NI_free(nel->vec) ; nel->vec = NULL ;
361         }
362 
363         return nel ;   /* default: return element */
364       }
365 
366       /*-- If here, must read data from the buffer into nel->vec --*/
367 
368       /* Find the form of the input */
369 
370       form = NI_TEXT_MODE ; /* default is text mode */
371       swap = 0 ;            /* and (obviously) don't byte swap */
372 
373       ii = string_index( "ni_form" , nel->attr_num , nel->attr_lhs ) ;
374       if( ii >= 0 && nel->attr_rhs[ii] != NULL ){ /* parse ni_form=rhs */
375 
376          /* binary or base64 mode? */
377 
378          if( strstr(nel->attr_rhs[ii],"binary") != NULL )
379             form = NI_BINARY_MODE ;
380          else if( strstr(nel->attr_rhs[ii],"base64") != NULL ){
381             form = NI_BASE64_MODE ;
382             ns->b64_numleft = 0 ;    /* 21 Apr 2005: reset Base64 leftovers */
383          }
384 
385          /* check byteorder in header vs. this CPU */
386 
387          if( form != NI_TEXT_MODE ){
388             int order=NI_MSB_FIRST ; /* default input byteorder */
389             if( strstr(nel->attr_rhs[ii],"lsb") != NULL ) order = NI_LSB_FIRST;
390             swap = ( order != NI_byteorder() ) ;  /* swap bytes? */
391          }
392       }
393 
394       /*-- 13 Feb 2003: Use new NI_read_columns() function to get data. --*/
395 
396       if( form == NI_TEXT_MODE ) ii = NI_LTEND_MASK ;  /* end on '<' char  */
397       else if( swap )            ii = NI_SWAP_MASK  ;  /* swap binary data */
398       else                       ii = 0 ;              /* no special flag  */
399 
400       row = NI_read_columns( ns ,
401                              nel->vec_num, nel->vec_typ,
402                              nel->vec_len, nel->vec    , form, ii );
403 
404       nel->vec_filled = (row >= 0) ? row : 0 ;
405       /* 27 Mar 2003: allow for case where vec_len is
406                       inferred from how much data we read */
407 
408       if( nel->vec_len == 0 ){
409         if( nel->vec_axis_len == NULL )
410           nel->vec_axis_len = NI_malloc(int, sizeof(int)) ;
411 
412         nel->vec_axis_len[0] = nel->vec_len  = nel->vec_filled ;
413         nel->vec_rank = 1 ;
414       }
415 
416       /*-- Now scan for the end-of-element marker '</something>' and
417            skip all input bytes up to (and including) the final '>'. --*/
418 
419       num_restart = 0 ;
420 TailRestart:
421       num_restart++ ;
422 
423       if( num_restart < 99 ){  /* don't loop forever, dude */
424          int is_tail ;
425 
426 #ifdef NIML_DEBUG
427 NI_dpr("NI_read_element: TailRestart scan_for_angles; num_restart=%d\n" ,
428                num_restart ) ;
429 #endif
430 
431          nn = scan_for_angles( ns , 99 ) ;  /* find '<...>' */
432 
433          /* if we didn't find '<...>' at all,
434             then if the I/O stream is bad, just exit;
435             otherwise, try scanning for '<...>' again */
436 
437          if( nn < 0 ){
438            if( NI_stream_readcheck(ns,0) < 0 ) return nel ;
439            goto TailRestart ;
440          }
441 
442          /* we have '<...>', but make sure it starts with '</' */
443 
444          is_tail = ( ns->buf[ns->npos+1] == '/' ) ;
445 
446          if( !is_tail ){                         /* no '/'? */
447            ns->npos = nn ; NI_reset_buffer(ns) ; /* skip '<...>' */
448            goto TailRestart ;                    /* and try again */
449          }
450 
451          ns->npos = nn ; /* skip '</...>' and we are done here! */
452       }
453 
454       /*-- And are done with the input stream and the data element! --*/
455 
456 #ifdef NIML_DEBUG
457 NI_dpr("NI_read_element: returning filled data element\n") ;
458 #endif
459 
460       /*-- 23 Aug 2002: do something, instead of returning data? --*/
461 
462       if( strcmp(nel->name,"ni_do") == 0 ){
463         NI_do( ns , nel ) ;
464         NI_free_element( nel ) ;
465         num_restart = 0 ; goto HeadRestart ;
466       }
467 
468       return nel ;
469 
470    } /* end of reading data element */
471 
472    return NULL ; /* should never be reached */
473 }
474 
475 /*----------------------------------------------------------------------*/
476 
477 #undef  NVBUF
478 #define NVBUF 127  /* max num chars for one number */
479 
480 #define IS_USELESS(c) ( isspace(c) || iscntrl(c) )
481 #define IS_CRLF(c)    ( (c) == 0x0D || (c) == 0x0A )
482 
483 /*----------------------------------------------------------------------*/
484 /*! From the NI_stream ns, starting at buffer position ns->npos, decode
485     one number into *val.
486     - Parameter ltend != 0 means to stop at '<' character [07 Jan 2003].
487     - ltend != 0 also means to skip lines starting with '#' [20 Mar 2003].
488     - ns->npos will be altered to reflect the current buffer position
489       (one after the last character processed) when all is done.
490     - Return value of this function is 1 if we succeeded, 0 if not.
491 ------------------------------------------------------------------------*/
492 
NI_decode_one_double(NI_stream_type * ns,double * val,int ltend)493 int NI_decode_one_double( NI_stream_type *ns, double *val , int ltend )
494 {
495    int epos , num_restart, need_data, nn ;
496    char vbuf[NVBUF+1] ;                    /* number string from buffer */
497 
498    /*-- check inputs for stupidness --*/
499 
500    if( ns == NULL || ns->bad == MARKED_FOR_DEATH || val == NULL ) return 0 ;
501 
502    /*--- might loop back here to check if have enough data for a number ---*/
503 
504    num_restart = 0 ;
505 Restart:
506    num_restart++ ; need_data = 0 ;
507    if( num_restart > 19 ) return 0 ;  /*** too much ==> give up ***/
508 
509 #ifdef NIML_DEBUG
510 NI_dpr(" {restart: npos=%d nbuf=%d}",ns->npos,ns->nbuf) ;
511 #endif
512 
513    /*-- advance over useless characters in the buffer --*/
514 
515    while( ns->npos < ns->nbuf && IS_USELESS(ns->buf[ns->npos]) ) ns->npos++ ;
516 
517    /*-- check if we ran into the closing '<' prematurely
518         (before any useful characters); if we did, then we are done --*/
519 
520    if( ltend && ns->npos < ns->nbuf && ns->buf[ns->npos] == '<' ) return 0 ;
521 
522    /*-- 20 Mar 2003: check if we ran into a comment character '#';
523                      if we did, skip to the end of the line (or '<') --*/
524 
525    if( ltend && ns->npos < ns->nbuf && ns->buf[ns->npos] == '#' ){
526      int npold = ns->npos ;
527      while( ns->npos < ns->nbuf && !IS_CRLF(ns->buf[ns->npos]) ){
528        if( ns->buf[ns->npos] == '<' ) return 0 ;  /* STOP HERE! */
529        ns->npos++ ;
530      }
531      if( ns->npos < ns->nbuf ){ /* found end of line, so try again */
532        num_restart = 0 ; goto Restart ;
533      }
534      /* if here, didn't find '<' or end of line in buffer */
535      /* so reset pointer back to '#', then read more data */
536      ns->npos = npold ; need_data = 1 ;
537    }
538 
539    /*-- if we need some data, try to get some --*/
540 
541    if( !need_data )                        /* need at least 2 unused  */
542      need_data = (ns->nbuf-ns->npos < 2) ; /* bytes to decode a number */
543 
544    /*-- An input value is decoded from a string of non-useless
545         characters delimited by a useless character (or by the
546         element closing '<').
547         Note that the 1st character we are now at is non-useless.
548         Scan forward to see if we have a useless character later. --*/
549 
550    if( !need_data ){  /* so have at least 2 characters */
551 
552 #ifdef NIML_DEBUG
553 nn = ns->nbuf-ns->npos ; if( nn > 19 ) nn = 19 ;
554 NI_dpr(" {buf=%.*s}" , nn , ns->buf+ns->npos ) ;
555 #endif
556 
557       for( epos=ns->npos+1 ; epos < ns->nbuf ; epos++ )
558         if( ns->buf[epos] == '<' || IS_USELESS(ns->buf[epos]) ) break ;
559 
560       /*- epos is either the delimiter position, or the end of data bytes -*/
561 
562       need_data = (epos == ns->nbuf) ; /* no delimiter ==> need more data */
563 
564 #ifdef NIML_DEBUG
565 if( need_data ) NI_dpr(" {eob}") ;
566 #endif
567 
568       /*- If the string of characters we have is not yet
569           delimited, and it is too long to be a number,
570           throw out all the data in the buffer and quit. -*/
571 
572       if( need_data && epos-ns->npos > NVBUF ){ clear_buffer(ns); return 0; }
573    }
574 
575    /*-- read more data now if it is needed --*/
576 
577    if( need_data ){
578 
579       NI_reset_buffer(ns) ; /* discard used up data in buffer */
580 
581       /*- read at least 1 byte,
582           waiting up to 666 ms (unless the data stream goes bad) -*/
583 
584 #ifdef NIML_DEBUG
585 NI_dpr(" {fill buf}") ;
586 #endif
587       nn = NI_stream_fillbuf( ns , 1 , 666 ) ;
588 
589       if( nn >= 0 ) goto Restart ;  /* check if buffer is adequate now */
590 
591       /*- if here, the stream went bad.  If there are still
592           data bytes in the stream, we can try to interpret them.
593           Otherwise, must quit without success.                  -*/
594 
595       if( ns->nbuf == 0 ){ ns->npos=0; return 0; }  /* quitting */
596 
597       epos = ns->nbuf ;
598    }
599 
600    /*-- if here, try to interpret data bytes ns->npos .. epos-1 --*/
601 
602    nn = epos-ns->npos ; if( nn > NVBUF ) nn = NVBUF ;     /* # bytes to read   */
603    memcpy( vbuf, ns->buf+ns->npos, nn ); vbuf[nn] = '\0'; /* put bytes in vbuf */
604    *val = 0.0 ;                                           /* initialize val */
605    sscanf( vbuf , "%lf" , val ) ;                         /* interpret them    */
606    ns->npos = epos ; return 1 ;                           /* retire undefeated */
607 }
608 
609 /*----------------------------------------------------------------------*/
610 /*! From the NI_stream ns, starting at buffer position ns->npos, decode
611     one string into newly NI_malloc()-ed space pointed to by *str.
612     - Parameter ltend !=0 means to stop at '<' character [07 Jan 2003].
613     - ltend != 0 also means to skip lines starting with '#' [20 Mar 2003].
614     - Return value of this function is 1 if we succeeded, 0 if not.
615     - ns->npos will be altered to reflect the current buffer position
616       (one after the last character processed) when all is done.
617 ------------------------------------------------------------------------*/
618 
NI_decode_one_string(NI_stream_type * ns,char ** str,int ltend)619 int NI_decode_one_string( NI_stream_type *ns, char **str , int ltend )
620 {
621    int epos , num_restart, need_data, nn , overbuf=0 ;
622    intpair sp ;
623 
624    /*-- check inputs for stupidness --*/
625 
626    if( ns == NULL || ns->bad == MARKED_FOR_DEATH || str == NULL ) return 0 ;
627 
628    /*--- might loop back here to check if have enough data ---*/
629 
630    num_restart = 0 ;
631 Restart:
632    num_restart++ ; need_data = 0 ;
633    if( num_restart > 19 ){
634      if( overbuf ){         /* 21 Nov 2007: warn if buffer was too small */
635        static int nov=0 ;
636        if( ++nov < 7 )
637          fprintf(stderr,"** ERROR: String runs past end of NIML buffer\n");
638      }
639      return 0 ;  /*** give up ***/
640    }
641    if( num_restart > 2 && overbuf ){  /* 23 Nov 2007: auto-expand buffer */
642      nn = 2*NI_stream_getbufsize(ns) ;
643      if( nn > 0 ){
644 #if 0
645        static int nov=0 ;
646        if( ++nov < 7 )
647          fprintf(stderr,"** WARNING: long String expands NIML buffer to %d bytes\n",nn) ;
648 #endif
649        nn = NI_stream_setbufsize( ns , nn ) ;       /* double down */
650        if( nn < 0 ) return 0 ; /*** buffer expand fails? give up ***/
651      }
652    }
653    overbuf = 0 ;
654 
655    /*-- advance over useless characters in the buffer --*/
656 
657    while( ns->npos < ns->nbuf && IS_USELESS(ns->buf[ns->npos]) ) ns->npos++ ;
658 
659    /*-- check if we ran into the closing '<' prematurely
660         (before any useful characters); if we did, then we are done --*/
661 
662    if( ltend && ns->npos < ns->nbuf && ns->buf[ns->npos] == '<' ) return 0 ;
663 
664    /*-- 20 Mar 2003: check if we ran into a comment character '#';
665                      if we did, skip to the end of the line (or '<') --*/
666 
667    if( ltend && ns->npos < ns->nbuf && ns->buf[ns->npos] == '#' ){
668      int npold = ns->npos ;
669      while( ns->npos < ns->nbuf && !IS_CRLF(ns->buf[ns->npos]) ){
670        if( ns->buf[ns->npos] == '<' ) return 0 ;  /* STOP HERE! */
671        ns->npos++ ;
672      }
673      if( ns->npos < ns->nbuf ){ /* found end of line, so try again */
674        num_restart = 0 ; goto Restart ;
675      }
676      /* if here, didn't find '<' or end of line in buffer */
677      /* so reset pointer back to '#', then read more data */
678      ns->npos = npold ; need_data = 1 ;
679    }
680 
681    /*-- if we need some data, try to get some --*/
682 
683    if( !need_data )                        /* need at least 2 unused  */
684      need_data = (ns->nbuf-ns->npos < 2) ; /* bytes to decode a string */
685 
686    if( !need_data ){  /* so have at least 2 characters */
687 
688       /* search for the string from here forward */
689 
690       sp = find_string( ns->npos , ns->nbuf , ns->buf ) ;
691 
692       need_data = (sp.i < 0)        ||  /* didn't find a string */
693                   (sp.j <= sp.i)    ||  /* ditto */
694                   (sp.j == ns->nbuf)  ; /* hit end of data bytes */
695 
696       overbuf = (sp.j == ns->nbuf) ; /* 21 Nov 2007: flag buffer overrun */
697    }
698 
699    /*-- read more data now if it is needed --*/
700 
701    if( need_data ){
702 
703       NI_reset_buffer(ns) ; /* discard used up data in buffer */
704 
705       /*- read at least 1 byte,
706           waiting up to 666 ms (unless the data stream goes bad) -*/
707 
708       nn = NI_stream_fillbuf( ns , 1 , 666 ) ;
709 
710       if( nn >= 0 ) goto Restart ;  /* check if buffer is adequate now */
711 
712       /*- if here, the stream went bad.  If there are still
713           data bytes in the stream, we can try to interpret them.
714           Otherwise, must quit without success.                  -*/
715 
716       if( ns->nbuf == 0 ){ ns->npos=0; return 0; }  /* quitting */
717 
718       sp.i = 0 ; sp.j = ns->nbuf ;
719    }
720 
721    /*-- if here, data bytes sp.i .. sp.j-1 are the string --*/
722 
723    nn = sp.j - sp.i ;                       /* length of string */
724    *str = NI_malloc(char, nn+1) ;           /* make the string */
725    memcpy( *str , ns->buf+sp.i , nn ) ;     /* copy data to string */
726    (*str)[nn] = '\0' ;                      /* terminate string */
727 
728    /* skip close quote character, if present */
729 
730    if( sp.j < ns->nbuf && IS_QUOTE_CHAR(ns->buf[sp.j]) ) sp.j++ ;
731 
732    ns->npos = sp.j ; return 1 ;
733 }
734 
735 /*----------------------------------------------------------------------*/
736 /*! Reset the unscanned bytes in the buffer to start at position 0
737     instead of position ns->npos; then set ns->npos to 0.
738 ------------------------------------------------------------------------*/
739 
NI_reset_buffer(NI_stream_type * ns)740 void NI_reset_buffer( NI_stream_type *ns )
741 {
742    if( ns == NULL || ns->npos <= 0 || ns->nbuf <= 0 ) return ;
743    if( ns->buf == NULL || ns->bad == MARKED_FOR_DEATH ) return ;
744 
745    if( ns->npos < ns->nbuf ){          /* haven't used up all data yet */
746      memmove( ns->buf, ns->buf+ns->npos, ns->nbuf-ns->npos ) ;
747      ns->nbuf -= ns->npos ;
748    } else {
749      ns->nbuf = 0 ;                   /* all data in buffer is used up */
750    }
751    ns->npos = 0 ;              /* further scanning starts at beginning */
752 }
753 
754 /*----------------------------------------------------------------------*/
755 /*! Scan stream for an element header or trailer:'<characters>',
756     starting at byte offset ns->npos, and waiting msec milliseconds.
757 
758     Returns with the stream buffer set so that the opening '<' is at
759     ns->buf[ns->npos] and the closing '>' is at ns->buf[q-1], where q
760     is this function's return value.  Note that read operations may
761     change ns->npos from its value when this function was called.
762 
763     If the return value is -1, then we couldn't find a '<stuff>' string.
764     This may be due to:
765       - there is no '<...>' in the buffer, and we can't read from
766          the input stream; call NI_readcheck(ns,0) to confirm this
767       - time ran out (alas)
768       - The '<...' part filled the entire buffer space.  In this case,
769          all the input buffer is thrown away - we don't support
770          headers or trailers this long!
771 
772     01 Jun 2005: skip XML comments, which are of the form
773                  "<!-- arbitrary text -->".
774 ------------------------------------------------------------------------*/
775 
scan_for_angles(NI_stream_type * ns,int msec)776 static int scan_for_angles( NI_stream_type *ns, int msec )
777 {
778    int nn, epos, need_data, num_restart ;
779    char goal ;
780    int start_time = NI_clock_time() , mleft , nbmin ;
781    int caseb=0 ;  /* 1 => force rescan even if time is up */
782 
783 #ifdef NIML_DEBUG
784 NI_dpr("ENTER scan_for_angles\n") ;
785 #endif
786 
787    if( ns == NULL ) return -1 ;  /* bad input */
788 
789    if( ns->buf == NULL || ns->bad == MARKED_FOR_DEATH ) return -1 ;
790 
791    epos = ns->npos ;
792 
793    if( msec < 0 ) msec = 999999999 ;   /* a long time (11+ days) */
794 
795    /*-- Will loop back here if we have to re-read/re-scan --*/
796 
797    goal        = '<' ;  /* first goal is opening '<' (second goal is '>') */
798    num_restart = 0   ;
799 Restart:                                       /* loop back here to retry */
800    num_restart++ ;
801    mleft = msec - (NI_clock_time()-start_time) ;             /* time left */
802 
803    if( num_restart > 3 && mleft <= 0 && !caseb ){              /* failure */
804       NI_reset_buffer(ns) ;                            /* and out of time */
805       return -1 ;
806    }
807 #ifdef NIML_DEBUG
808 NI_dpr("  scan_for_angles at restart=%d epos=%d bufsize=%d",num_restart,epos,ns->nbuf) ;
809 #endif
810 
811    /*-- scan ahead to find goal character in the buffer --*/
812 
813    while( epos < ns->nbuf && ns->buf[epos] != goal ) epos++ ;
814 
815    /*-- if we found our goal, do something about it --*/
816 
817    if( epos < ns->nbuf ){
818 
819 #ifdef NIML_DEBUG
820 NI_dpr("  scan_for_angles found goal '%c' at epos=%d",goal,epos) ;
821 #endif
822 
823      /*-- if our goal was the closing '>', we are done! (maybe) --*/
824 
825      if( goal == '>' ){
826 
827        /*- 01 Jun 2005: see if we are at a comment; if so, must start over -*/
828 
829        if( epos - ns->npos >= 4 && strncmp(ns->buf+ns->npos,"<!--",4) == 0 ){
830 
831          if( strncmp(ns->buf+epos-2,"-->",3) == 0 ){  /* got a full comment */
832 
833 #if 0
834 { int ncp = 1+epos-ns->npos ; char *cpt=malloc(10+ncp) ;
835   memcpy(cpt,ns->buf+ns->npos,ncp) ; cpt[ncp] = '\0' ;
836   fprintf(stderr, "\nSkipping NIML comment: '%s'\n",cpt); free(cpt);
837 }
838 #endif
839 
840            ns->npos = epos+1 ; NI_reset_buffer(ns) ;  /* skip it & try again */
841            epos = 0 ; goal = '<' ;
842          } else {                              /* '>' doesn't close comment! */
843            epos++ ;                            /* so look for another one!!! */
844          }
845          caseb = 1 ; goto Restart ;
846        }
847 
848        /*** not a comment, so we can exit triumphantly! ***/
849 
850        return epos+1 ;  /* marks the character after '>' */
851      }
852 
853      /*-- if here, our goal was the opening '<';
854           set the buffer position to this location,
855           set the new goal, and scan for the new goal --*/
856 
857       ns->npos = epos ;  /* mark where we found '<' */
858       goal     = '>'  ;  /* the new goal */
859       caseb    = 1    ;  /* force rescan, even if time is up */
860       goto Restart    ;  /* scan again! */
861    }
862 
863    /*-- if we get to here, we didn't find our goal:
864         (a) if the goal was the opening '<', then throw
865              away all data in the buffer, and get some more data
866         (b) if the goal was the closing '>', then we need more data
867             in the buffer, but need to keep the existing data
868         (c) UNLESS the buffer is full AND npos is zero
869              - in this case, we expand the buffer size and hope --*/
870 
871    if( goal == '<' ){                    /* case (a) */
872 #ifdef NIML_DEBUG
873 NI_dpr("  scan_for_angles failed to find goal '<' in buffer of size %d",ns->nbuf) ;
874 #endif
875       ns->nbuf = ns->npos = epos = 0 ; caseb = 0 ;
876 
877    } else if( ns->nbuf < ns->bufsize || ns->npos > 0 ){  /* case (b) */
878 #ifdef NIML_DEBUG
879 NI_dpr("  scan_for_angles failed to find goal '>' -- retry") ;
880 #endif
881       NI_reset_buffer(ns) ; epos = ns->nbuf ; caseb = 1 ;
882 
883    } else {                              /* case (c) */
884 #ifdef NIML_DEBUG
885 NI_dpr("  scan_for_angles failed to find goal '>' -- expand buffer and retry") ;
886 #endif
887       epos = ns->nbuf ;
888       nn = NI_stream_setbufsize(ns,2*ns->bufsize) ; /* expand buffer! */
889       if( nn < 0 ){ ns->nbuf = ns->npos = 0 ; return -1 ; } /* fails? */
890 
891    }
892 
893    /*-- if we are here, we need more data before scanning again --*/
894 
895    /*-- read at least nbmin bytes,
896         waiting up to mleft ms (unless the data stream goes bad) --*/
897 
898    if( mleft <= 0 ) mleft = 3 ;
899    nbmin = (goal == '<') ? 4 : 1 ;
900 
901 #ifdef NIML_DEBUG
902 NI_dpr("  scan_for_angles calling NI_stream_fillbuf") ;
903 #endif
904    nn = NI_stream_fillbuf( ns , nbmin , mleft ) ;
905 
906    if( nn >= nbmin ) caseb = 1 ;    /* got new data => force rescan */
907 
908    if( nn >= 0     ) goto Restart ; /* scan some more for the goal */
909 
910    /*-- if here, the stream went bad, so exit --*/
911 #ifdef NIML_DEBUG
912 NI_dpr("  scan_for_angles stream failed :(") ;
913 #endif
914    ns->nbuf = ns->npos = 0 ; return -1 ;
915 }
916 
917 /*------------------------------------------------------------------------*/
918 /*! Mode for writing names. */
919 
920 static int name_mode = NI_NAMEMODE_NORMAL ;
921 
922 /*------------------------------------------------------------------------*/
923 /*! Set the mode for writing type names:
924      - NI_NAMEMODE_NORMAL => byte , short, int  , float  , double , ...
925      - NI_NAMEMODE_ALIAS  => uint8, int16, int32, float32, float64, ...
926 --------------------------------------------------------------------------*/
927 
NI_set_typename_mode(int nmode)928 void NI_set_typename_mode( int nmode )
929 {
930    if( nmode > 0 && nmode <= NI_ATTMODE_LAST ) name_mode = nmode ;
931    else                                        name_mode = NI_NAMEMODE_NORMAL;
932 }
933 
934 /*------------------------------------------------------------------------*/
935 /*! Return the type name given the integer code. */
936 
NI_type_name(int code)937 char * NI_type_name( int code )
938 {
939    return (name_mode == NI_NAMEMODE_ALIAS) ? NI_rowtype_code_to_alias(code)
940                                            : NI_rowtype_code_to_name (code) ;
941 }
942 
943 /*------------------------------------------------------------------------*/
944 /*! Write an element (data or group) to a stream.
945     Return value is number of bytes written to the stream.
946     If return is -1, something bad happened.  You should then check
947     the stream with NI_stream_goodcheck(), for example.
948 
949     If the stream is temporarily unable to write (e.g., the socket
950     buffer is full), then this function will wait until it is ready.
951     If you don't want that behavior, you should use NI_stream_writecheck()
952     before calling this function.
953 --------------------------------------------------------------------------*/
954 
NI_write_element(NI_stream_type * ns,void * nini,int tmode)955 int64_t NI_write_element( NI_stream_type *ns , void *nini , int tmode )
956 {
957    char *wbuf , *att=NULL , *qtt , *btt ;
958    int  nwbuf , ii,jj,row,col , tt=NI_element_type(nini) ;
959    int  att_len , kk , otmode=tmode ;
960    int64_t ntot=0 , nout ;             /* 28 Jun 2021 */
961 
962    char *bbuf , *cbuf ;  /* base64 stuff */
963    int   bb=0 ,  cc=0 ;
964 
965    char *att_prefix , *att_equals , *att_trail ;
966    int header_only , header_sharp , outmode=-1 ;
967 
968    /*--- 09 Mar 2005: outmode overrides tmode, if outmode is present ---*/
969 
970    switch( tt ){
971      default: return -1 ;    /* bad input! */
972 
973      case NI_GROUP_TYPE:{
974        NI_group *ngr = (NI_group *) nini ;
975        outmode = ngr->outmode ;
976      }
977      break ;
978 
979      case NI_ELEMENT_TYPE:{
980        NI_element *nel = (NI_element *) nini ;
981        outmode = nel->outmode ;
982      }
983      break ;
984 
985      case NI_PROCINS_TYPE:{       /* 16 Mar 2005 */
986        outmode = NI_TEXT_MODE ;
987      }
988      break ;
989    }
990    if( outmode >= 0 ) tmode = outmode ;
991 
992    /*--- determine special cases from the flags above bit #7 ---*/
993 
994    header_only  = ((tmode & NI_HEADERONLY_FLAG ) != 0) ;  /* 20 Feb 2003 */
995    header_sharp = ((tmode & NI_HEADERSHARP_FLAG) != 0) ;  /* 20 Mar 2003 */
996 
997    /* ADDOUT = after writing, add byte count if OK, else quit */
998    /* AF     = thing to do if ADDOUT is quitting */
999 
1000 #ifdef NIML_DEBUG
1001 NI_dpr("ENTER NI_write_element\n") ;
1002 #endif
1003 
1004 #undef  AF
1005 #define AF
1006 #define ADDOUT(q) if(nout<0){AF;fprintf(stderr,"NIML: write abort %s nout=%lld\n",q,nout);return -1;} else ntot+=nout
1007 
1008    if( !NI_stream_writeable(ns) ) return -1 ;  /* stupid user */
1009 
1010    if( ns->bad ){                        /* socket that hasn't connected yet */
1011 #ifdef NIML_DEBUG
1012 NI_dpr("NI_write_element: write socket not connected\n") ;
1013 #endif
1014       jj = NI_stream_goodcheck(ns,666) ; /* try to connect it */
1015       if( jj < 1 ) return jj ;           /* 0 is nothing yet, -1 is death */
1016 #ifdef NIML_DEBUG
1017 NI_dpr("NI_write_element: write socket now connected\n") ;
1018 #endif
1019    } else {                              /* check if good ns has gone bad */
1020       jj = NI_stream_writecheck(ns,666) ;
1021       if( jj < 0 ) return jj ;
1022    }
1023 
1024    tmode &= 255 ;
1025    if( ns->type == NI_STRING_TYPE )      /* string output only in text mode */
1026       tmode = NI_TEXT_MODE ;
1027 
1028    if( tmode != NI_TEXT_MODE ) header_sharp = 0 ;  /* 20 Mar 2003 */
1029 
1030    /*-- 15 Oct 2002: write attributes with lots of space, or little --*/
1031    /*-- 20 Mar 2003: modified for "#  lhs = rhs" type of header     --*/
1032 
1033    att_prefix = (header_sharp) ? (char *)"\n#  " /* write this before each attribute */
1034                                : (char *)"\n  " ;
1035 
1036    att_equals = (header_sharp) ? (char *)" = "   /* write this between lhs and rhs */
1037                                : (char *)"="    ;
1038 
1039    att_trail  = (header_sharp) ? (char *)"\n# "  /* write this before closing ">" */
1040                                : (char *)" "    ;
1041 
1042    /*------------------ write a processing instruction ------------------*/
1043 
1044    if( tt == NI_PROCINS_TYPE ){
1045 
1046      NI_procins *npi = (NI_procins *)nini ;
1047 
1048      if( header_sharp ){ nout = NI_stream_writestring(ns,"# "); ADDOUT("a"); }
1049 
1050      nout = NI_stream_writestring( ns , "<?"   )    ; ADDOUT("b") ;
1051      nout = NI_stream_writestring( ns , npi->name ) ; ADDOUT("c") ;
1052 
1053      /*- attributes -*/
1054 
1055      for( ii=0 ; ii < npi->attr_num ; ii++ ){
1056 
1057        jj = NI_strlen( npi->attr_lhs[ii] ) ; if( jj == 0 ) continue ;
1058        nout = NI_stream_writestring( ns , " " ) ; ADDOUT("d") ;
1059        if( NI_is_name(npi->attr_lhs[ii]) ){
1060          nout = NI_stream_write( ns , npi->attr_lhs[ii] , jj ) ;
1061        } else {
1062          att = quotize_string( npi->attr_lhs[ii] ) ;
1063          nout = NI_stream_writestring( ns , att ) ; NI_free(att) ;
1064        }
1065        ADDOUT("e") ;
1066 
1067        jj = NI_strlen( npi->attr_rhs[ii] ) ; if( jj == 0 ) continue ;
1068        nout = NI_stream_writestring( ns , "=" ) ; ADDOUT("f") ;
1069        att = quotize_string( npi->attr_rhs[ii] ) ;
1070        nout = NI_stream_writestring( ns , att ) ; NI_free(att) ; ADDOUT("g") ;
1071      }
1072 
1073      nout = NI_stream_writestring( ns , " ?>\n" ) ; ADDOUT("h") ;
1074 
1075      return ntot ;   /*** done with processing instruction ***/
1076 
1077    /*------------------ write a group element ------------------*/
1078 
1079    } else if( tt == NI_GROUP_TYPE ){
1080 
1081       NI_group *ngr = (NI_group *) nini ;
1082       char *gname ;
1083 
1084       /* 24 Feb 2005: all group elements used to be named "ni_group",
1085                       but no more; now they have attribute ni_form="ni_group" */
1086 
1087       gname = ngr->name ;
1088       if( gname == NULL || *gname == '\0' ) gname = "ni_group" ;
1089 
1090       /*- group header -*/
1091 
1092       if( header_sharp ){ nout = NI_stream_writestring(ns,"# "); ADDOUT("i"); }
1093 #if 1
1094       nout = NI_stream_writestring( ns , "<"   ) ; ADDOUT("j") ;
1095       nout = NI_stream_writestring( ns , gname ) ; ADDOUT("k") ;
1096 #else
1097       nout = NI_stream_writestring( ns , "<ni_group" ) ; ADDOUT("l") ;
1098 #endif
1099 
1100       /*- attributes -*/
1101 
1102       NI_set_attribute( ngr , "ni_form" , "ni_group" ) ;  /* 24 Feb 2005 */
1103 
1104       for( ii=0 ; ii < ngr->attr_num ; ii++ ){
1105 
1106         jj = NI_strlen( ngr->attr_lhs[ii] ) ; if( jj == 0 ) continue ;
1107         nout = NI_stream_writestring( ns , att_prefix ) ; ADDOUT("m") ;
1108         if( NI_is_name(ngr->attr_lhs[ii]) ){
1109           nout = NI_stream_write( ns , ngr->attr_lhs[ii] , jj ) ;
1110         } else {
1111           att = quotize_string( ngr->attr_lhs[ii] ) ;
1112           nout = NI_stream_writestring( ns , att ) ; NI_free(att) ;
1113         }
1114         ADDOUT("n") ;
1115 
1116         jj = NI_strlen( ngr->attr_rhs[ii] ) ; if( jj == 0 ) continue ;
1117         nout = NI_stream_writestring( ns , att_equals ) ; ADDOUT("o") ;
1118         att = quotize_string( ngr->attr_rhs[ii] ) ;
1119         nout = NI_stream_writestring( ns , att ) ; NI_free(att) ; ADDOUT("p") ;
1120       }
1121 
1122       /*- close group header -*/
1123 
1124       nout = NI_stream_writestring( ns , att_trail ) ; ADDOUT("q") ;
1125       nout = NI_stream_writestring( ns , ">\n" ) ; ADDOUT("r") ;
1126 
1127       /*- write the group parts (recursively) -*/
1128 
1129       for( ii=0 ; ii < ngr->part_num ; ii++ ){
1130         if( outmode >= 0 ){
1131           if( NI_element_type(ngr->part[ii]) == NI_ELEMENT_TYPE ){
1132             NI_element *qel = (NI_element *)ngr->part[ii] ;
1133             qel->outmode = outmode ;
1134           } else if( NI_element_type(ngr->part[ii]) == NI_GROUP_TYPE ){
1135             NI_group *qgr = (NI_group *)ngr->part[ii] ;
1136             qgr->outmode = outmode ;
1137           }
1138         }
1139         nout = NI_write_element( ns , ngr->part[ii] , otmode ) ; ADDOUT("s") ;
1140       }
1141 
1142       /*- group trailer -*/
1143 
1144       if( header_sharp ){ nout = NI_stream_writestring(ns,"# "); ADDOUT("t"); }
1145 #if 1
1146       nout = NI_stream_writestring( ns , "</"  ) ; ADDOUT("u") ;
1147       nout = NI_stream_writestring( ns , gname ) ; ADDOUT("v") ;
1148       nout = NI_stream_writestring( ns , ">\n" ) ; ADDOUT("w") ;
1149 #else
1150       nout = NI_stream_writestring( ns , "</ni_group>\n" ) ; ADDOUT("x") ;
1151 #endif
1152 
1153       return ntot ;   /*** done with group element ***/
1154 
1155    /*------------------ write a data element ------------------*/
1156 
1157    } else if( tt == NI_ELEMENT_TYPE ){
1158 
1159       NI_element *nel = (NI_element *) nini ;
1160 
1161       /*- sanity check (should never fail) -*/
1162 
1163       jj = NI_strlen(nel->name) ; if( jj == 0 ) return -1 ;
1164 
1165       /*- select the data output mode -*/
1166 
1167       /* Strings can only be written in text mode */
1168 
1169       if( tmode != NI_TEXT_MODE ){
1170         for( jj=0 ; jj < nel->vec_num ; jj++ ){
1171           if( NI_has_String(NI_rowtype_find_code(nel->vec_typ[jj])) ){
1172              tmode = NI_TEXT_MODE ; break ;
1173           }
1174         }
1175       }
1176 
1177       switch( tmode ){
1178          default: tmode = NI_TEXT_MODE ; break ;
1179 
1180          case NI_BINARY_MODE: break ;
1181          case NI_BASE64_MODE: break ;
1182       }
1183 
1184       /* space to hold attribute strings */
1185 
1186       att_len = 8192 + 256*nel->vec_num + 128*nel->vec_rank ;
1187       att     = NI_malloc(char, att_len ) ;
1188 
1189       /* create ni_veclab attribute if needed [12 Sep 2018] */
1190 
1191       NI_set_attribute_from_veclab_array( nel , nel->vec_lab ) ;
1192 
1193 #undef  AF
1194 #define AF NI_free(att)  /* free att if we have to quit early now */
1195 
1196       /* write start of header "<name" */
1197 
1198       if( header_sharp ){ nout = NI_stream_writestring(ns,"# "); ADDOUT("y"); }
1199       strcpy(att,"<") ; strcat(att,nel->name) ;
1200       nout = NI_stream_writestring( ns , att ) ; ADDOUT("z") ;
1201 
1202       /*- write "special" attributes, if not an empty element -*/
1203 
1204       if( nel->vec_len > 0 && nel->vec_num > 0 ){
1205          int ll , tt ;
1206 
1207          /* ni_form (depends on tmode) */
1208 
1209          switch( tmode ){
1210            default:
1211            case NI_TEXT_MODE:
1212              *att = '\0' ;   /* text form is default */
1213            break ;
1214 
1215            case NI_BINARY_MODE:
1216            case NI_BASE64_MODE:
1217              sprintf(att,"%sni_form%s\"%s.%s\"" ,
1218                     att_prefix , att_equals ,
1219                     (tmode == NI_BINARY_MODE)      ? "binary"   : "base64"  ,
1220                     (NI_byteorder()==NI_LSB_FIRST) ? "lsbfirst" : "msbfirst" );
1221             break ;
1222          }
1223          if( *att != '\0' ){
1224             nout = NI_stream_writestring( ns , att ) ; ADDOUT("A") ;
1225          }
1226 
1227          /** do ni_type **/
1228 
1229          sprintf(att,"%sni_type%s\"" , att_prefix , att_equals ) ;
1230          for( ll=-1,ii=0 ; ii < nel->vec_num ; ii++ ){
1231           if( nel->vec_typ[ii] != ll ){  /* not the previous type */
1232              if( ll >= 0 ){              /* write the previous type out now */
1233                 btt = att + strlen(att) ;
1234                 if( jj > 1 ) sprintf(btt,"%d*%s,",jj,NI_type_name(ll)) ;
1235                 else         sprintf(btt,"%s,"   ,   NI_type_name(ll)) ;
1236              }
1237              ll = nel->vec_typ[ii] ;     /* save new type code */
1238              jj = 1 ;                    /* it now has count 1 */
1239 
1240           } else {                       /* same as previous type */
1241              jj++ ;                      /* so add 1 to its count */
1242           }
1243          }
1244          /* write the last type we found */
1245          btt = att + strlen(att) ;
1246          if( jj > 1 ) sprintf(btt,"%d*%s\"",jj,NI_type_name(ll)) ;
1247          else         sprintf(btt,"%s\""   ,   NI_type_name(ll)) ;
1248 
1249          nout = NI_stream_writestring( ns , att ) ; ADDOUT("B") ;
1250 
1251          /** do ni_dimen **/
1252 
1253          if( nel->vec_rank > 1 ){
1254            sprintf(att,"%sni_dimen%s" , att_prefix , att_equals ) ;
1255            qtt = quotize_int_vector( nel->vec_rank ,
1256                                    nel->vec_axis_len , ',' ) ;
1257            strcat(att,qtt) ; NI_free(qtt) ;
1258          } else {
1259            sprintf(att,"%sni_dimen%s\"%d\"",att_prefix,att_equals,nel->vec_len);
1260          }
1261          nout = NI_stream_writestring( ns , att ) ; ADDOUT("C") ;
1262 
1263 #if 0
1264          /** 26 Mar 2003: write number of bytes of data contained herein **/
1265 
1266          for( jj=ii=0 ; ii < nel->vec_num ; ii++ )
1267             jj += NI_size_column( NI_rowtype_find_code(nel->vec_typ[ii]) ,
1268                                   nel->vec_len , nel->vec[ii] ) ;
1269          sprintf(att,"%sni_datasize%s\"%d\"" , att_prefix , att_equals , jj ) ;
1270          nout = NI_stream_writestring( ns , att ) ; ADDOUT("D") ;
1271 #endif
1272 
1273 #if 0
1274          /* extras: ni_veclen and ni_vecnum attributes */
1275 
1276          sprintf(att,"%sni_veclen%s\"%d\"", att_prefix,att_equals,nel->vec_len) ;
1277          nout = NI_stream_writestring( ns , att ) ; ADDOUT("E") ;
1278 
1279          sprintf(att,"%sni_vecnum%s\"%d\"", att_prefix,att_equals,nel->vec_num) ;
1280          nout = NI_stream_writestring( ns , att ) ; ADDOUT("F") ;
1281 #endif
1282          /* ni_delta */
1283 
1284          if( nel->vec_axis_delta != NULL ){
1285             sprintf(att,"%sni_delta%s",att_prefix,att_equals) ;
1286             qtt = quotize_float_vector( nel->vec_rank ,
1287                                         nel->vec_axis_delta , ',' ) ;
1288             strcat(att,qtt) ; NI_free(qtt) ;
1289             nout = NI_stream_writestring( ns , att ) ; ADDOUT("G") ;
1290          }
1291 
1292          /* ni_origin */
1293 
1294          if( nel->vec_axis_origin != NULL ){
1295             sprintf(att,"%sni_origin%s",att_prefix,att_equals) ;
1296             qtt = quotize_float_vector( nel->vec_rank ,
1297                                         nel->vec_axis_origin , ',' ) ;
1298             strcat(att,qtt) ; NI_free(qtt) ;
1299             nout = NI_stream_writestring( ns , att ) ; ADDOUT("H") ;
1300          }
1301 
1302          /* ni_units */
1303 
1304          if( nel->vec_axis_unit != NULL ){
1305             sprintf(att,"%sni_units%s",att_prefix,att_equals) ;
1306             qtt = quotize_string_vector( nel->vec_rank ,
1307                                          nel->vec_axis_unit , ',' ) ;
1308             strcat(att,qtt) ; NI_free(qtt) ;
1309             nout = NI_stream_writestring( ns , att ) ; ADDOUT("I") ;
1310          }
1311 
1312          /* ni_axes */
1313 
1314          if( nel->vec_axis_label != NULL ){
1315             sprintf(att,"%sni_axes%s",att_prefix,att_equals) ;
1316             qtt = quotize_string_vector( nel->vec_rank ,
1317                                          nel->vec_axis_label , ',' ) ;
1318             strcat(att,qtt) ; NI_free(qtt) ;
1319             nout = NI_stream_writestring( ns , att ) ; ADDOUT("J") ;
1320          }
1321 
1322       }
1323 
1324       /*- other attributes -*/
1325 
1326       for( ii=0 ; ii < nel->attr_num ; ii++ ){
1327 
1328          jj = NI_strlen( nel->attr_lhs[ii] ) ; if( jj == 0 ) continue ;
1329 
1330          /* skip "special" attributes */
1331 
1332          if( strcmp(nel->attr_lhs[ii],"ni_form")     == 0 ) continue ;
1333          if( strcmp(nel->attr_lhs[ii],"ni_type")     == 0 ) continue ;
1334          if( strcmp(nel->attr_lhs[ii],"ni_dimen")    == 0 ) continue ;
1335          if( strcmp(nel->attr_lhs[ii],"ni_veclen")   == 0 ) continue ;
1336          if( strcmp(nel->attr_lhs[ii],"ni_vecnum")   == 0 ) continue ;
1337          if( strcmp(nel->attr_lhs[ii],"ni_delta")    == 0 ) continue ;
1338          if( strcmp(nel->attr_lhs[ii],"ni_origin")   == 0 ) continue ;
1339          if( strcmp(nel->attr_lhs[ii],"ni_units")    == 0 ) continue ;
1340          if( strcmp(nel->attr_lhs[ii],"ni_axes")     == 0 ) continue ;
1341          if( strcmp(nel->attr_lhs[ii],"ni_datasize") == 0 ) continue ; /* 13 Apr 2004 */
1342 
1343          kk = NI_strlen( nel->attr_rhs[ii] ) ;
1344 
1345          /* do the work */
1346 
1347          if( jj+kk+128 > att_len ){                 /* 13 Jun 2003 */
1348            att_len = jj+kk+128 ;
1349            att     = NI_realloc( att , char, att_len ) ;
1350          }
1351 
1352          strcpy(att,att_prefix) ;
1353 
1354          if( NI_is_name(nel->attr_lhs[ii]) ){           /* the 'normal' case */
1355            strcat(att,nel->attr_lhs[ii]) ;
1356          } else {                                        /* not legal in XML */
1357            qtt = quotize_string( nel->attr_lhs[ii] ) ;
1358            strcat(att,qtt) ; NI_free(qtt) ;
1359          }
1360 
1361          if( kk > 0 ){
1362             strcat(att,att_equals) ;
1363             qtt = quotize_string( nel->attr_rhs[ii] ) ; /* RHS always quoted */
1364             kk = strlen(qtt)+strlen(att)+32 ;
1365             if( kk > att_len ){ att_len=kk; att=NI_realloc(att,char,att_len); }
1366             strcat(att,qtt) ; NI_free(qtt) ;
1367          }
1368          nout = NI_stream_writestring( ns , att ) ; ADDOUT("K") ;
1369       }
1370 
1371       NI_free(att) ; att = NULL ; /**** done with attributes ****/
1372 
1373 #undef  AF
1374 #define AF    /* nothing to do if we have to quit early */
1375 
1376       /*- close header -*/
1377 
1378       if( nel->vec_len == 0    ||     /* An 'empty' element (no data) */
1379           nel->vec_num == 0    ||
1380           nel->vec_typ == NULL ||
1381           nel->vec     == NULL   ){
1382 
1383         nout = NI_stream_writestring( ns , att_trail ) ; ADDOUT("L") ;
1384         nout = NI_stream_writestring( ns , "/>\n" )    ; ADDOUT("M") ;
1385 
1386 #ifdef NIML_DEBUG
1387   NI_dpr("NI_write_element: empty element '%s' had %d total bytes\n",nel->name,ntot) ;
1388 #endif
1389         return ntot ;                 /*** done with empty data element ***/
1390       }
1391 
1392       /*- if here, must write some data out -*/
1393 
1394       /* first, terminate the header,
1395          and allocate space for the write buffer (1 row at a time) */
1396 
1397       switch( tmode ){
1398          default:
1399          case NI_TEXT_MODE:
1400             btt = ">\n" ;                             /* add a newline */
1401          break ;
1402 
1403          case NI_BINARY_MODE:
1404             btt = ">" ;                               /* no newline   */
1405          break ;
1406 
1407          case NI_BASE64_MODE:
1408             btt = ">\n" ;                             /* add a newline */
1409          break ;
1410       }
1411 
1412       nout = NI_stream_writestring( ns , att_trail ) ; ADDOUT("N") ;
1413       nout = NI_stream_writestring( ns , btt ) ; ADDOUT("O") ;
1414 
1415       /*-- 13 Feb 2003: data output is now done elsewhere --*/
1416 
1417       if( !header_only ){
1418         nout = NI_write_columns( ns, nel->vec_num, nel->vec_typ,
1419                                      nel->vec_len, nel->vec    , tmode ) ;
1420         ADDOUT("DATA") ;
1421       }
1422 #ifdef NIML_DEBUG
1423       else NI_dpr("NI_write_element: header_only case\n") ;
1424 #endif
1425 
1426       /*- write element trailer -*/
1427 
1428       if( header_sharp ){ nout = NI_stream_writestring(ns,"# "); ADDOUT("Q"); }
1429       nout = NI_stream_writestring( ns , "</" )      ; ADDOUT("R") ;
1430       nout = NI_stream_writestring( ns , nel->name ) ; ADDOUT("S") ;
1431       nout = NI_stream_writestring( ns , ">\n\n" )   ; ADDOUT("T") ;
1432 
1433 #ifdef NIML_DEBUG
1434   NI_dpr("NI_write_element: data element '%s' had %d total bytes\n",nel->name,ntot) ;
1435 #endif
1436       return ntot ;   /*** done with full data element ***/
1437 
1438    } /* end of write data element */
1439 
1440    return -1 ; /* should never be reachable */
1441 }
1442 
1443 /*------------------------------------------------------------------------*/
1444 /*! Write an element (data or group) to a file.  [07 Mar 2007]
1445 --------------------------------------------------------------------------*/
1446 
NI_write_element_tofile(char * fname,void * nini,int tmode)1447 int64_t NI_write_element_tofile( char *fname , void *nini , int tmode )
1448 {
1449    NI_stream_type *ns ; char *nsname ; int64_t vv ;
1450 
1451    if( fname == NULL || *fname == '\0' || nini == NULL ) return -1 ;
1452 
1453    nsname = (char *)malloc(strlen(fname)+9) ;
1454    if( strncmp(fname,"stdout:",7) == 0 || strcmp(fname,"-") == 0 ){
1455      strcpy(nsname,"stdout:") ;
1456    } else if( strncmp(fname,"stderr:",7) == 0 ){
1457      strcpy(nsname,"stderr:") ;
1458    } else {
1459      strcpy(nsname,"file:") ; strcat(nsname,fname) ;
1460    }
1461    ns = NI_stream_open( nsname , "w" ) ; free((void *)nsname) ;
1462    if( ns == NULL ){ fprintf(stderr,"NIML: fail to open file %s for writing\n",fname); return -1; }
1463    vv = NI_write_element( ns , nini , tmode ) ;
1464    NI_stream_close( ns ) ;
1465    return vv ;
1466 }
1467 
1468 /*------------------------------------------------------------------------*/
1469 /*! Read one element from a file.  [12 Mar 2007]
1470 --------------------------------------------------------------------------*/
1471 
NI_read_element_fromfile(char * fname)1472 void * NI_read_element_fromfile( char *fname )
1473 {
1474    NI_stream_type *ns ; char *nsname ; void *nini ;
1475 
1476    if( fname == NULL || *fname == '\0' ) return NULL ;
1477 
1478    if( NI_is_fifo(fname) ){               /* FIFO - only text element */
1479      char *buf = NI_suck_file( fname ) ;             /* [27 Aug 2019] */
1480      if( buf == NULL ) return NULL ;
1481      nini = NI_read_element_fromstring( buf ) ;
1482      free(buf) ;
1483      return nini ;
1484    }
1485 
1486    /* regular file - could have a binary element */
1487 
1488    nsname = (char *)malloc(strlen(fname)+9) ;
1489    strcpy(nsname,"file:") ; strcat(nsname,fname) ;
1490    ns = NI_stream_open( nsname , "r" ) ; free((void *)nsname) ;
1491    if( ns == NULL ) return NULL ;
1492    nini = NI_read_element( ns , 999 ) ;
1493    NI_stream_close( ns ) ;
1494    return nini ;
1495 }
1496 
1497 /*------------------------------------------------------------------------*/
1498 /*! Read one element from a string.  [26 Feb 2010 ZSS]
1499 --------------------------------------------------------------------------*/
NI_read_element_fromstring(char * nstr)1500 void * NI_read_element_fromstring( char *nstr )
1501 {
1502    NI_stream_type *ns ;  void *nini ;
1503 
1504    if( nstr == NULL || *nstr == '\0' ) return (NULL) ;
1505 
1506    /* convert string to a NIML element */
1507 
1508    ns = NI_stream_open( "str:" , "r" ) ;
1509    NI_stream_setbuf( ns , nstr ) ;
1510    nini = NI_read_element( ns , 1 ) ;
1511    NI_stream_close( ns ) ;
1512 
1513    return (nini) ;
1514 }
1515 
1516 /*------------------------------------------------------------------------*/
1517 /*! Write one element to a string.  [Oct 2011 ZSS, based on Bob's]
1518 --------------------------------------------------------------------------*/
NI_write_element_tostring(void * nel)1519 char * NI_write_element_tostring( void *nel )
1520 {
1521    NI_stream ns ;
1522    char *stout ;
1523    int ii,jj ;
1524 
1525    if( nel == NULL ) return (NULL) ;
1526 
1527    ns = NI_stream_open( "str:" , "w" ) ;
1528    (void) NI_write_element( ns , nel , NI_TEXT_MODE ) ;
1529    stout = strdup( NI_stream_getbuf(ns) ) ;
1530    NI_stream_close( ns ) ;
1531    jj = strlen(stout) ;
1532    for( ii=jj-1 ; ii > 0 && isspace(stout[ii]) ; ii-- ) ; /* trailing blanks */
1533    stout[ii+1] = '\0' ;
1534    return (stout) ;
1535 }
1536