source: trunk/src/nxio.c @ 1822

Revision 1788, 18.2 KB checked in by Freddie Akeroyd, 6 months ago (diff)

Use size_t rather than int to allow larger file sizes in write. Refs #315

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/**
2 * This file contains functions necessary to perform XML-I/O for
3 * NeXus with the mxml-library.
4 *
5 * Most notably it contains the callback function for reading and
6 * writing  data
7 *
8 *   Copyright (C) 2004 Mark Koennecke
9 *
10 *  This library is free software; you can redistribute it and/or
11 *  modify it under the terms of the GNU Lesser General Public
12 *  License as published by the Free Software Foundation; either
13 *  version 2 of the License, or (at your option) any later version.
14 *
15 *  This library is distributed in the hope that it will be useful,
16 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 *  Lesser General Public License for more details.
19 *
20 *  You should have received a copy of the GNU Lesser General Public
21 *  License along with this library; if not, write to the Free Software
22 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23 *
24 *  For further information, see <http://www.nexusformat.org>
25 */
26
27#ifdef NXXML
28
29#include <mxml.h>
30#include <assert.h>
31#include "napi.h"
32#include "nxio.h"
33#include "nxdataset.h"
34#include "napiconfig.h"
35
36/* fix for mxml-2.3 */
37#ifndef MXML_WRAP
38#define MXML_WRAP 79
39#endif
40
41#ifdef _MSC_VER
42#define snprintf _snprintf
43#endif /* _MSC_VER */
44
45/* #define TESTMAIN 1 */
46/*=================== type code handling ================================= */
47typedef struct {
48  char name[30];
49  char format[30];
50  int  nx_type;
51}type_code;
52
53#define NTYPECODE 11
54static type_code typecode[NTYPECODE];
55/*-----------------------------------------------------------------------*/
56void initializeNumberFormats(){
57  type_code myCode;
58
59  strcpy(myCode.name,"NX_FLOAT32");
60  strcpy(myCode.format,"%12.4f");
61  myCode.nx_type = NX_FLOAT32;
62  typecode[0] = myCode;
63
64  strcpy(myCode.name,"NX_FLOAT64");
65  strcpy(myCode.format,"%16.5f");
66  myCode.nx_type = NX_FLOAT64;
67  typecode[1] = myCode;
68
69  strcpy(myCode.name,"NX_INT8");
70  strcpy(myCode.format,"%5d");
71  myCode.nx_type = NX_INT8;
72  typecode[2] = myCode;
73
74  strcpy(myCode.name,"NX_UINT8");
75  strcpy(myCode.format,"%5d");
76  myCode.nx_type = NX_UINT8;
77  typecode[3] = myCode;
78
79  strcpy(myCode.name,"NX_INT16");
80  strcpy(myCode.format,"%8d");
81  myCode.nx_type = NX_INT16;
82  typecode[4] = myCode;
83
84  strcpy(myCode.name,"NX_UINT16");
85  strcpy(myCode.format,"%8d");
86  myCode.nx_type = NX_UINT16;
87  typecode[5] = myCode;
88
89  strcpy(myCode.name,"NX_INT32");
90  strcpy(myCode.format,"%12d");
91  myCode.nx_type = NX_INT32;
92  typecode[6] = myCode;
93
94  strcpy(myCode.name,"NX_UINT32");
95  strcpy(myCode.format,"%12d");
96  myCode.nx_type = NX_UINT32;
97  typecode[7] = myCode;
98
99  strcpy(myCode.name,"NX_INT64");
100  strcpy(myCode.format,"%24lld");
101  myCode.nx_type = NX_INT64;
102  typecode[8] = myCode;
103
104  strcpy(myCode.name,"NX_UINT64");
105  strcpy(myCode.format,"%24llu");
106  myCode.nx_type = NX_UINT64;
107  typecode[9] = myCode;
108
109  strcpy(myCode.name,"NX_CHAR");
110  strcpy(myCode.format,"%c");
111  myCode.nx_type = NX_CHAR;
112  typecode[10] = myCode;
113}
114/*----------------------------------------------------------------------*/
115void setNumberFormat(int nx_type, char *format){
116  int i;
117
118  for(i = 0; i < NTYPECODE; i++){
119    if(typecode[i].nx_type == nx_type){
120      strncpy(typecode[i].format,format,29);
121    }
122  }
123}
124/*------------------------------------------------------------------*/
125static void getNumberFormat(int nx_type, char format[30]){
126  int i;
127
128  for(i = 0; i < NTYPECODE; i++){
129    if(typecode[i].nx_type == nx_type){
130      strncpy(format,typecode[i].format,29);
131    }
132  }
133}
134/*----------------------------------------------------------------*/
135void getNumberText(int nx_type, char *typestring, int typeLen){
136  int i;
137
138  for(i = 0; i < NTYPECODE; i++){
139    if(typecode[i].nx_type == nx_type){
140      strncpy(typestring,typecode[i].name,typeLen);
141    }
142  }
143}
144/*
145 * 'mxml_add_char()' - Add a character to a buffer, expanding as needed.
146 * copied here from mxml-file.c to achieve compatibility with mxml-2.1
147 * standard
148 */
149
150static int                              /* O  - 0 on success, -1 on error */
151myxml_add_char(int  ch,                 /* I  - Character to add */
152              char **bufptr,            /* IO - Current position in buffer */
153              char **buffer,            /* IO - Current buffer */
154              size_t  *bufsize)         /* IO - Current buffer size */
155{
156  char  *newbuffer;                     /* New buffer value */
157
158
159  if (*bufptr >= (*buffer + *bufsize - 4))
160  {
161   /*
162    * Increase the size of the buffer...
163    */
164
165    if (*bufsize < 1024)
166    {
167      (*bufsize) *= 2;
168    }
169    else
170    {
171      (*bufsize) *= 3;
172      (*bufsize) /= 2;
173    }
174
175    newbuffer = (char *)malloc(*bufsize*sizeof(char));
176    if(!newbuffer){
177      free(*buffer);
178
179      mxml_error("Unable to expand string buffer to %d bytes!", *bufsize);
180
181      return (-1);
182    }
183    memset(newbuffer,0,*bufsize*sizeof(char));
184    memcpy(newbuffer,*buffer,*bufptr - *buffer);
185    free(*buffer);
186
187    *bufptr = newbuffer + (*bufptr - *buffer);
188    *buffer = newbuffer;
189  }
190
191  if (ch < 128)
192  {
193   /*
194    * Single byte ASCII...
195    */
196
197    *(*bufptr)++ = ch;
198  }
199  else if (ch < 2048)
200  {
201   /*
202    * Two-byte UTF-8...
203    */
204
205    *(*bufptr)++ = 0xc0 | (ch >> 6);
206    *(*bufptr)++ = 0x80 | (ch & 0x3f);
207  }
208  else if (ch < 65536)
209  {
210   /*
211    * Three-byte UTF-8...
212    */
213
214    *(*bufptr)++ = 0xe0 | (ch >> 12);
215    *(*bufptr)++ = 0x80 | ((ch >> 6) & 0x3f);
216    *(*bufptr)++ = 0x80 | (ch & 0x3f);
217  }
218  else
219  {
220   /*
221    * Four-byte UTF-8...
222    */
223
224    *(*bufptr)++ = 0xf0 | (ch >> 18);
225    *(*bufptr)++ = 0x80 | ((ch >> 12) & 0x3f);
226    *(*bufptr)++ = 0x80 | ((ch >> 6) & 0x3f);
227    *(*bufptr)++ = 0x80 | (ch & 0x3f);
228  }
229
230  return (0);
231}
232/*------------------------------------------------------------------*/
233extern char *stptok(char *s, char *tok, size_t toklen, char *brk);
234/*=====================================================================
235 actual stuff for implementing the callback functions
236 =====================================================================*/
237
238/*
239 * if passed NX_CHAR, then returns dimension of -1 and the caller
240 * needs to do a strlen() or equivalent
241 */
242void analyzeDim(const char *typeString, int *rank, 
243                            int64_t *iDim, int *type){
244  char dimString[132];
245  char dim[20];
246  const char *dimStart, *dimEnd;
247  char* dimTemp;
248  int myRank;
249
250  if(strchr(typeString,(int)'[') == NULL){
251    *rank = 1;
252    switch(*type){
253    case NX_INT8:
254    case NX_UINT8:
255    case NX_INT16:
256    case NX_UINT16:
257    case NX_INT32:
258    case NX_UINT32:
259    case NX_INT64:
260    case NX_UINT64:
261    case NX_FLOAT32:
262    case NX_FLOAT64:
263      iDim[0] = 1;
264      break;
265    case NX_CHAR:
266      iDim[0] = -1;     /* length unknown, caller needs to determine later */
267      break;
268    default:
269      mxml_error("ERROR: (analyzeDim) unknown type code %d for typeString %s", *type, typeString);
270      break;
271    }
272  } else {
273    /*
274      we have to determine rank and the dims.
275      Start by extracting the dimension string.
276    */
277    dimStart = strchr(typeString,(int)'[') + 1;
278    dimEnd =  strchr(typeString,(int)']');
279    if(!dimStart || !dimEnd) {
280      mxml_error("ERROR: malformed dimension string in %s",typeString);
281      return;
282    }
283    if((dimEnd - dimStart) > 131){
284      mxml_error("ERROR: run away dimension definition in %s",typeString);
285      return;
286    }
287    memset(dimString,0,132);
288    memcpy(dimString,dimStart,(dimEnd-dimStart)*sizeof(char));
289    dimTemp = stptok(dimString,dim,19,",");
290    myRank = 0;
291    while(dimTemp != NULL){
292      iDim[myRank] = atoi(dim);
293      dimTemp = stptok(dimTemp,dim,19,",");
294      myRank++;
295    }
296    *rank = myRank;
297  }
298}
299/*--------------------------------------------------------------------*/
300int translateTypeCode(const char *code){
301  int i, result = -1;
302 
303  for(i = 0; i < NTYPECODE; i++){
304    if(strstr(code,typecode[i].name) != NULL){
305      result = typecode[i].nx_type;
306      break;
307    }
308  }
309  return result;
310}
311
312/*
313 * This is used to locate an Idims node from the new style table data layout
314 */
315static mxml_node_t* findDimsNode(mxml_node_t *node)
316{
317    mxml_node_t *tnode = NULL;
318    const char* name = node->value.element.name;
319    if ( (node->parent != NULL) && !strcmp(node->parent->value.element.name, DATA_NODE_NAME) )
320    {
321        tnode = mxmlFindElement(node->parent->parent, node->parent->parent, DIMS_NODE_NAME, NULL, NULL, MXML_DESCEND_FIRST);
322        if (tnode != NULL)
323        {
324            tnode = mxmlFindElement(tnode,tnode,name,NULL,NULL,MXML_DESCEND_FIRST);
325        }
326    }
327    return tnode;
328}
329
330/*---------------------------------------------------------------------*/
331/*return 1 if in table mode , 0 if not */
332static void analyzeDataType(mxml_node_t *parent, int *rank, int *type,
333                            int64_t *iDim){
334  const char *typeString;
335  mxml_node_t* tnode;
336  int nx_type = -1;
337  int table_mode = 0;
338
339  *rank = 1;
340  *type = NX_CHAR;
341  iDim[0] = -1;
342
343  /*
344    get the type attribute. No attribute means: plain text
345  */ 
346  tnode = findDimsNode(parent);
347  if (tnode != NULL)
348  {
349        table_mode = 1;
350        parent = tnode;
351  }
352  typeString = mxmlElementGetAttr(parent,TYPENAME);
353  if(typeString == NULL){
354    return;
355  }
356
357  nx_type = translateTypeCode((char *)typeString);
358
359  /*
360    assign type
361  */
362  if(nx_type == -1){
363    mxml_error(
364     "ERROR: %s is an invalid NeXus type, I try to continue but may fail",
365     typeString);
366    *type =NX_CHAR;
367    return;
368  }
369
370  *type = nx_type;
371 
372  analyzeDim(typeString, rank, iDim, type);
373  if (table_mode)
374  {
375        *rank = 1;
376        iDim[0] = 1;
377  }
378  return;
379}
380/*-------------------------------------------------------------------*/
381void destroyDataset(void *data){
382  if(data != NULL){
383    dropNXDataset((pNXDS)data);
384  }
385}
386/*-------------------------------------------------------------------*/
387static char *getNextNumber(char *pStart, char pNumber[80]){
388  int charCount = 0;
389  pNumber[0] = '\0';
390
391  /* advance to first digit */
392  while(isspace(*pStart) && *pStart != '\0'){
393    pStart++;
394  }
395  if(*pStart == '\0'){
396    return NULL;
397  }
398
399  /* copy */
400  while(!isspace(*pStart) && *pStart != '\0' && charCount < 78){
401    pNumber[charCount] = *pStart;
402    pStart++;
403    charCount++;
404  }
405  pNumber[charCount] = '\0';
406  return pStart;
407}
408/*--------------------------------------------------------------------*/
409mxml_type_t nexusTypeCallback(mxml_node_t *parent){
410  const char *typeString;
411
412  if(strstr(parent->value.element.name,"?xml") != NULL ||
413     !strncmp(parent->value.element.name,"NX",2) ||
414     !strcmp(parent->value.element.name,DATA_NODE_NAME) ||
415     !strcmp(parent->value.element.name,DIMS_NODE_NAME)){
416    return MXML_ELEMENT;
417  } else {
418    /* data nodes do not habe TYPENAME in table style but are always CUSTOM */
419    if (parent->parent != NULL && !strcmp(parent->parent->value.element.name, DATA_NODE_NAME))
420    {
421        return MXML_CUSTOM;
422    }
423    if (parent->parent != NULL && !strcmp(parent->parent->value.element.name, DIMS_NODE_NAME))
424    {
425        return MXML_OPAQUE;
426    }
427    typeString = mxmlElementGetAttr(parent,TYPENAME);
428    if(typeString == NULL){
429      /*
430        MXML_TEXT seems more appropriate here. But mxml hacks text into
431        single words which is not what NeXus wants.
432      */
433      return MXML_OPAQUE;
434    } else{
435      if(strstr(typeString,"NX_CHAR") != NULL){
436        return MXML_OPAQUE;
437      } else {
438        return MXML_CUSTOM;
439      }
440    }
441  }
442}
443/*----------------------------------------------------------------------*/
444int nexusLoadCallback(mxml_node_t *node, const char *buffer){
445  mxml_node_t *parent = NULL;
446  int rank, type; 
447  int64_t iDim[NX_MAXRANK];
448  char pNumber[80], *pStart;
449  long address, maxAddress;
450  pNXDS dataset = NULL;
451
452  parent = node->parent;
453  analyzeDataType(parent,&rank,&type,iDim);
454  if(iDim[0] == -1 || !strcmp(parent->parent->value.element.name, DIMS_NODE_NAME)){
455    iDim[0] = strlen(buffer);
456    node->value.custom.data = strdup(buffer);
457    node->value.custom.destroy = free;
458    return 0;
459  } else {
460    node->value.custom.data = createNXDataset(rank,type,iDim);
461    dataset = (pNXDS)node->value.custom.data;
462    if(dataset == NULL){
463      mxml_error("Failed to allocate custom dataset");
464      return 1;
465    }
466    node->value.custom.destroy = destroyDataset; 
467  }
468
469  /*
470    load data
471  */
472  pStart = (char *)buffer;
473  maxAddress = getNXDatasetLength(dataset);
474  address = 0;
475  while( (pStart = getNextNumber(pStart,pNumber)) != NULL && 
476         address < maxAddress){
477    putNXDatasetValueAt(dataset,address,atof(pNumber));
478    address++;
479  }
480
481  return 0;
482}
483/*---------------------------------------------------------------------*/
484static void stringIntoBuffer(char **buffer, char **bufPtr, size_t *bufSize, 
485                      char *string){
486  size_t i;
487
488  for(i = 0; i < strlen(string); i++){
489    myxml_add_char(string[i],bufPtr,buffer,bufSize);
490  }
491}
492/*--------------------------------------------------------------------*/
493static void formatNumber(double value, char *txt, int txtLen,
494                         char *format, int type){
495  switch(type){
496  case NX_INT8:
497  case NX_UINT8:
498  case NX_INT16:
499  case NX_UINT16:
500  case NX_INT32:
501  case NX_UINT32:
502    snprintf(txt,txtLen,format,(int)value);
503    break;
504  case NX_INT64:
505    snprintf(txt,txtLen,format,(int64_t)value);
506    break;
507  case NX_UINT64:
508    snprintf(txt,txtLen,format,(uint64_t)value);
509    break;
510  case NX_FLOAT32:
511  case NX_FLOAT64:
512    snprintf(txt,txtLen,format,value);
513    break;
514  default:
515    /*assert(0);  something is very wrong here */
516    printf("Problem\n");
517    break;
518  }
519}
520/*--------------------------------------------------------------------*/
521static int countDepth(mxml_node_t *node){
522  int count = 0;
523  mxml_node_t *cur;
524
525  cur = node;
526  while(cur != NULL){
527    count++;
528    cur = cur->parent;
529  }
530  count--;
531  return count;
532}
533/*---------------------------------------------------------------------*/
534char *nexusWriteCallback(mxml_node_t *node){
535  int type, col;
536  char pNumber[80], indent[80], format[30];
537  char *buffer, *bufPtr;
538  pNXDS dataset;
539  int currentLen, table_style = 0; 
540  size_t i, bufsize, length;
541  int is_definition = 0;
542  /* this is set by nxconvert when making a definiton */
543  is_definition = (getenv("NX_IS_DEFINITION") != NULL);
544
545  if (!strcmp(node->parent->parent->value.element.name, DATA_NODE_NAME))
546  {
547        table_style = 1;
548  }
549  /*
550    allocate output buffer
551  */
552  buffer = (char *)malloc(1024*sizeof(char));
553  if(buffer == NULL){
554    mxml_error("Unable to allocate buffer");
555    return NULL;
556  }
557  memset(buffer,0,1024);
558  bufPtr = buffer;
559  bufsize = 1024;
560
561  dataset = (pNXDS)node->value.custom.data;
562
563  /*
564    prepare indentation level
565  */
566  col = countDepth(node)*2;
567  memset(indent,0,80);
568  for(i = 0; i < col; i++){
569    indent[i] = ' ';
570  }
571
572  /*
573    get dataset info
574  */
575  type = getNXDatasetType(dataset);
576  if (is_definition) {
577    length = 1;
578  } else {
579    length = getNXDatasetLength(dataset);
580  }
581  if(dataset->format != NULL){
582    strcpy(format,dataset->format);
583  } else {
584    getNumberFormat(type,format);
585  }
586
587  /*
588    actually get the data out
589  */
590  if (table_style)
591  {
592      for(i = 0; i < length; i++){
593        formatNumber(getNXDatasetValueAt(dataset,i),pNumber,79,format,type);
594        stringIntoBuffer(&buffer,&bufPtr,&bufsize,pNumber);
595      }
596  }
597  else
598  {
599      currentLen = col;
600      myxml_add_char('\n',&bufPtr,&buffer,&bufsize);
601      stringIntoBuffer(&buffer,&bufPtr,&bufsize,indent);
602      for(i = 0; i < length; i++){
603        formatNumber(getNXDatasetValueAt(dataset,i),pNumber,79,format,type);
604        if(currentLen + strlen(pNumber) > MXML_WRAP){
605          /*
606            wrap line
607          */
608          myxml_add_char('\n',&bufPtr,&buffer,&bufsize);
609          stringIntoBuffer(&buffer,&bufPtr,&bufsize,indent);
610          currentLen = col;
611        }
612        stringIntoBuffer(&buffer,&bufPtr,&bufsize,pNumber);
613        myxml_add_char(' ',&bufPtr,&buffer,&bufsize);
614        currentLen += strlen(pNumber) + 1;
615      }
616  }
617  myxml_add_char('\0',&bufPtr,&buffer,&bufsize);
618  return (char *)buffer;
619}
620/*------------------------------------------------------------------*/
621int isDataNode(mxml_node_t *node){
622  if(mxmlElementGetAttr(node,"name") != NULL){
623    return 0;
624  }
625  if(strcmp(node->value.element.name,"NXroot") == 0){
626    return 0;
627  }
628  if(strcmp(node->value.element.name,DIMS_NODE_NAME) == 0){
629    return 0;
630  }
631  if(strcmp(node->value.element.name,DATA_NODE_NAME) == 0){
632    return 0;
633  }
634  if(strcmp(node->value.element.name,"NAPIlink") == 0){
635    return 0;
636  }
637  return 1;
638}
639/*--------------------------------------------------------------------*/
640static int isTextData(mxml_node_t *node){
641  const char *attr = NULL;
642
643  if(!isDataNode(node)){
644    return 0;
645  }
646  /*
647    test datasets
648  */
649  attr = mxmlElementGetAttr(node,TYPENAME);
650  if(attr == NULL){
651    return 1;
652  }
653  if(strstr(attr,"NX_CHAR") != NULL){
654    return 1;
655  } else {
656    return 0;
657  }
658}
659/*---------------------------------------------------------------------*/
660
661/*
662 * note: not reentrant or thead safe; returns pointer to static storage
663 */
664const char *NXwhitespaceCallback(mxml_node_t *node, int where){
665  static char *indent = NULL;
666  int len; 
667
668  if(strstr(node->value.element.name,"?xml") != NULL){
669    return NULL;
670  }
671  if (node->parent != NULL && !strcmp(node->parent->value.element.name, DATA_NODE_NAME))
672  {
673    return NULL;
674  }
675  if (where == MXML_WS_BEFORE_CLOSE && !strcmp(node->value.element.name, DATA_NODE_NAME))
676  {
677    return NULL;
678  }
679
680  if(isTextData(node)){
681    if(where == MXML_WS_BEFORE_OPEN){
682      len = countDepth(node)*2 + 2;
683      if (indent != NULL)
684      {
685        free(indent);
686        indent = NULL;
687      }
688      indent = (char *)malloc(len*sizeof(char));
689      if(indent != NULL){
690        memset(indent,' ',len);
691        indent[0]= '\n';
692        indent[len-1] = '\0';
693        return  (const char*)indent;
694      }
695    }
696    return NULL;
697  }
698
699  if(where == MXML_WS_BEFORE_OPEN || where == MXML_WS_BEFORE_CLOSE){
700    len = countDepth(node)*2 + 2;
701    if (indent != NULL)
702    {
703        free(indent);
704        indent = NULL;
705    }
706    indent = (char *)malloc(len*sizeof(char));
707    if(indent != NULL){
708      memset(indent,' ',len);
709      indent[0]= '\n';
710      indent[len-1] = '\0';
711      return  (const char*)indent;
712    }
713  }
714  return NULL;
715}
716/*-----------------------------------------------------------------------*/
717#ifdef TESTMAIN
718#include <stdio.h>
719
720int main(int argc, char *argv[]){
721   mxml_node_t *root = NULL;
722   FILE *f;
723
724   mxmlSetCustomHandlers(nexusLoadCallback, nexusWriteCallback);
725   initializeNumberFormats();
726
727   /*
728     read test
729   */
730   f = fopen("dmc.xml","r");
731   root = mxmlLoadFile(NULL,f,nexusTypeCallback);
732   fclose(f);
733
734   /*
735     write test
736   */
737   setNumberFormat(NX_INT32,"%8d");
738   setNumberFormat(NX_FLOAT32,"%8.2f");
739   f = fopen("dmc2.xml","w");
740   mxmlSaveFile(root,f,NXwhitespaceCallback);
741   fclose(f);
742
743}
744#endif
745
746
747#endif /*NXXML*/ 
Note: See TracBrowser for help on using the repository browser.