/* ctx.c - Creativyst Table eXchange Format (CTX) parser */ /* Jon Mayo - December 2006 - PUBLIC DOMAIN - NO COPYRIGHT CLAIMED */ /* http://www.creativyst.com/Doc/Std/ctx/ctx.htm */ /* TODO: * - support line wrap \l * - support multibyte encoding \mx...; and \mb...; * - support \T and \G records field type * - support \L labels field type * - support \C comments field type * - support \N names field type * - support \H hovers field type * - support \P primary field type * - support \M mime field type * - support \E encoding field type * - support \Q SQL field type * - support \Y application specific field type * - support \K key field type * - support \X max size * - support \D display */ #include #include #include #include "proto.h" #include "errlog.h" #include "ctx.h" struct ctx_reader { char *filename; FILE *f; int current_line; int buffer_max; int buffer_len; char *buffer; int element_index; char *element_buffer; }; struct ctx_reader *ctx_open(const char *filename) { FILE *f; struct ctx_reader *cr; f=fopen(filename, "r"); if(!f) { PERROR(filename); return 0; } cr=malloc(sizeof *cr); cr->f=f; cr->current_line=0; cr->buffer_max=16; cr->buffer_len=0; cr->buffer=calloc(cr->buffer_max, 1); cr->filename=strdup(filename); cr->element_index=0; cr->element_buffer=0; return cr; } void ctx_close(struct ctx_reader *cr) { if(cr) { fclose(cr->f); free(cr->buffer); free(cr->filename); free(cr->element_buffer); free(cr); } else { printf("ctx_close called in error\n"); } } /* this starts another line, returns the line number. or 0 for EOF */ int ctx_nextline(struct ctx_reader *cr) { unsigned old_len; cr->buffer_len=0; /* empty the buffer */ cr->element_index=0; /* reset the element indexing */ do { /* +1 because there is a null terminator in buffer */ if(cr->buffer_max<=(cr->buffer_len+1)) { char *tmp; /* round up to next 4096 byte "page" */ cr->buffer_max=(cr->buffer_len+4095)/4096*4096; tmp=realloc(cr->buffer, cr->buffer_max); if(!tmp) { fprintf(stderr, "%s:%d:line too long, cannot allocate memory!\n", cr->filename, cr->current_line+1); return 0; } cr->buffer=tmp; } old_len=cr->buffer_len; if(!fgets(cr->buffer+old_len, cr->buffer_max-old_len, cr->f)) { /* the last line must end with a linefeed, or this code breaks! */ return 0; /* EOF */ } cr->buffer_len+=strlen(cr->buffer+old_len); } while(cr->buffer[cr->buffer_len-1] != '\n'); cr->buffer[cr->buffer_len-1]=0; return ++cr->current_line; } /* helper function for ctx_getelement() * allocates a buffer in cr, fills it with the unescaped string * returns: pointer to buffer in cr holding unescaped string. */ static const char *parse_escapes(struct ctx_reader *cr, const char *str) { int len, i; char *dst; free(cr->element_buffer); cr->element_buffer=0; /* we use the original str length as a guide because the escapes in CTX can * only make a string smaller when removed, not larger. */ len=strlen(str)+1; /* length in bytes - including terminating 0 */ dst=cr->element_buffer=malloc(len); for(i=0;ielement_buffer; } /* read a single element. * return: pointer to data as a string, or NULL on end of data list * this string is only valid until the next call of ctx_getelement() */ const char *ctx_getelement(struct ctx_reader *cr) { char *end; char *start; start=cr->buffer + cr->element_index; if(*start==0) return NULL; /* End-Of-Data */ end=strchr(start, '|'); if(!end) { /* last element on the line */ end=start+strlen(start); cr->element_index+=end-start; /* points at the '\0' */ } else { cr->element_index+=end-start+1; /* one character after the | */ } *end=0; return parse_escapes(cr, start); } /* helper function for ctx_write_escaped_string() */ static int empty_buffer(FILE *f, const char *str, int *start, int end, const char *esc) { int ret=0; if(*start!=end) ret=fwrite(str+*start, 1, end-*start, f); *start=end+1; fputs(esc, f); return ret; } /* utility to escape a string and write it to an open stream */ int ctx_write_escaped_string(FILE *f, const char *str) { int start, end; for(start=0,end=0;str[end];end++) { switch(str[end]) { case '\r': empty_buffer(f, str, &start, end, "\\r"); break; case '\n': empty_buffer(f, str, &start, end, "\\n"); break; case '|': empty_buffer(f, str, &start, end, "\\p"); break; case '\\': empty_buffer(f, str, &start, end, "\\i"); break; } } if(start!=end) fwrite(str+start, 1, end-start, f); return 1; } /* test code */ #ifdef UNIT_TEST int main(int argc, char **argv) { struct ctx_reader *cr; const char *elem; cr=ctx_open("users.ctx"); if(!cr) return EXIT_FAILURE; while(ctx_nextline(cr)) { printf("---\n"); while((elem=ctx_getelement(cr))) { printf("%s\n", elem); } } ctx_close(cr); ctx_write_escaped_string(stdout, "jaskdj\\|||\nk\r\t\t+ak4|"); printf("\n"); ctx_write_escaped_string(stdout, "jaskdj"); printf("\n"); } #endif