libcm is a C development framework with an emphasis on audio signal processing applications.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cmCsv.c 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233
  1. //| Copyright: (C) 2009-2020 Kevin Larke <contact AT larke DOT org>
  2. //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
  3. #include "cmPrefix.h"
  4. #include "cmGlobal.h"
  5. #include "cmRpt.h"
  6. #include "cmErr.h"
  7. #include "cmCtx.h"
  8. #include "cmMem.h"
  9. #include "cmMallocDebug.h"
  10. #include "cmLex.h"
  11. #include "cmLinkedHeap.h"
  12. #include "cmHashTbl.h"
  13. #include "cmCsv.h"
  14. #include "cmText.h"
  15. enum
  16. {
  17. kCommaLexTId = kUserLexTId+1,
  18. kMaxLexTId // always last in the lex id list
  19. };
  20. typedef struct
  21. {
  22. const char* text;
  23. unsigned id;
  24. } cmCsvToken_t;
  25. cmCsvToken_t _cmCsvTokenArray[] =
  26. {
  27. { ",", kCommaLexTId },
  28. { "", kErrorLexTId }
  29. };
  30. // The binder linked list contains a list of records which tracks the first
  31. // (left-most) non-blank column in each row.
  32. typedef struct cmCsvBind_str
  33. {
  34. struct cmCsvBind_str* linkPtr;
  35. cmCsvCell_t* rowPtr;
  36. } cmCsvBind_t;
  37. typedef struct cmCsvUdef_str
  38. {
  39. struct cmCsvUdef_str* linkPtr;
  40. unsigned id;
  41. } cmCsvUdef_t;
  42. typedef struct
  43. {
  44. cmErr_t err;
  45. void* rptDataPtr; //
  46. cmLexH lexH; // parsing lexer
  47. cmHashTblH_t htH; // hash table handle
  48. cmLHeapH_t heapH;
  49. cmCsvBind_t* bindPtr; // base of the binder linked list
  50. cmCsvCell_t* curRowPtr; // used by the parser to track the row being filled
  51. cmCsvUdef_t* udefList;
  52. } cmCsv_t;
  53. cmCsvH_t cmCsvNullHandle = { NULL };
  54. cmCsv_t* _cmCsvHandleToPtr( cmCsvH_t h )
  55. {
  56. cmCsv_t* p = (cmCsv_t*)h.h;
  57. assert( p != NULL );
  58. cmErrClearRC(&p->err);
  59. return p;
  60. }
  61. cmCsvRC_t _cmCsvVError( cmCsv_t* p, cmCsvRC_t rc, const char* fmt, va_list vl )
  62. { return cmErrVMsg(&p->err,rc,fmt,vl); }
  63. cmCsvRC_t _cmCsvError( cmCsv_t* p, cmCsvRC_t rc, const char* fmt, ... )
  64. {
  65. va_list vl;
  66. va_start(vl,fmt);
  67. rc = _cmCsvVError(p,rc,fmt,vl);
  68. va_end(vl);
  69. return rc;
  70. }
  71. cmCsvRC_t cmCsvInitialize( cmCsvH_t *hp, cmCtx_t* ctx )
  72. {
  73. cmCsvRC_t rc;
  74. cmCsv_t* p = NULL;
  75. unsigned i;
  76. if((rc = cmCsvFinalize(hp)) != kOkCsvRC )
  77. return rc;
  78. // create the base object
  79. p = cmMemAllocZ( cmCsv_t, 1 );
  80. assert(p != NULL);
  81. cmErrSetup(&p->err,&ctx->rpt,"CSV");
  82. // create the hash table
  83. if( cmHashTblCreate(ctx,&p->htH,8192) != kOkHtRC )
  84. {
  85. rc = _cmCsvError(p,kHashTblErrCsvRC,"Hash table creation failed.");
  86. goto errLabel;
  87. }
  88. // allocate the linked heap mgr
  89. if( cmLHeapIsValid(p->heapH = cmLHeapCreate(8192,ctx)) == false )
  90. {
  91. rc = _cmCsvError(p,kMemAllocErrCsvRC,"Linked heap object allocation failed.");
  92. goto errLabel;
  93. }
  94. // allocate the lexer
  95. if(cmLexIsValid(p->lexH = cmLexInit(NULL,0,0,&ctx->rpt)) == false )
  96. {
  97. rc = _cmCsvError(p,kLexErrCsvRC,"Lex allocation failed.");
  98. goto errLabel;
  99. }
  100. // register CSV specific tokens with the lexer
  101. for(i=0; _cmCsvTokenArray[i].id != kErrorLexTId; ++i)
  102. {
  103. cmRC_t lexRC;
  104. if( (lexRC = cmLexRegisterToken(p->lexH, _cmCsvTokenArray[i].id, _cmCsvTokenArray[i].text )) != kOkLexRC )
  105. {
  106. rc = _cmCsvError(p,kLexErrCsvRC,"Lex token registration failed for:'%s'.\nLexer Error:%s",_cmCsvTokenArray[i].text, cmLexRcToMsg(lexRC) );
  107. goto errLabel;
  108. }
  109. }
  110. hp->h = p;
  111. return kOkCsvRC;
  112. errLabel:
  113. cmMemPtrFree(&p);
  114. if( cmLHeapIsValid(p->heapH) )
  115. cmLHeapDestroy(&p->heapH);
  116. if( cmLexIsValid(p->lexH) )
  117. cmLexFinal(&p->lexH);
  118. return rc;
  119. }
  120. cmCsvRC_t cmCsvFinalize( cmCsvH_t *hp )
  121. {
  122. cmRC_t lexRC;
  123. if( hp == NULL || hp->h == NULL )
  124. return kOkCsvRC;
  125. cmCsv_t* p = _cmCsvHandleToPtr(*hp);
  126. // free the internal heap object
  127. cmLHeapDestroy( &p->heapH );
  128. // free the lexer
  129. if((lexRC = cmLexFinal(&p->lexH)) != kOkLexRC )
  130. return _cmCsvError(p,kLexErrCsvRC,"Lexer finalization failed.\nLexer Error:%s",cmLexRcToMsg(lexRC));
  131. // free the hash table
  132. cmHashTblDestroy(&p->htH);
  133. // free the handle
  134. cmMemPtrFree(&hp->h);
  135. return kOkCsvRC;
  136. }
  137. cmCsvRC_t cmCsvInitializeFromFile( cmCsvH_t *hp, const char* fn, unsigned maxRowCnt, cmCtx_t* ctx )
  138. {
  139. cmCsvRC_t rc;
  140. if((rc = cmCsvInitialize(hp,ctx)) != kOkCsvRC )
  141. return rc;
  142. return cmCsvParseFile( *hp, fn, maxRowCnt);
  143. }
  144. bool cmCsvIsValid( cmCsvH_t h)
  145. { return h.h != NULL; }
  146. cmCsvRC_t cmCsvLastRC( cmCsvH_t h )
  147. {
  148. cmCsv_t* p = _cmCsvHandleToPtr(h);
  149. return cmErrLastRC(&p->err);
  150. }
  151. void cmCsvClearRC( cmCsvH_t h )
  152. {
  153. cmCsv_t* p = _cmCsvHandleToPtr(h);
  154. cmErrClearRC(&p->err);
  155. }
  156. cmCsvRC_t cmCsvParseFile( cmCsvH_t h, const char* fn, unsigned maxRowCnt )
  157. {
  158. cmCsvRC_t rc = kOkCsvRC;
  159. FILE* fp = NULL;
  160. cmCsv_t* p = _cmCsvHandleToPtr(h);
  161. unsigned n = 0;
  162. char* textBuf = NULL;
  163. assert( fn != NULL && p != NULL );
  164. // open the file
  165. if((fp = fopen(fn,"rb")) == NULL )
  166. return _cmCsvError(p,kFileOpenErrCsvRC,"Unable to open the file:'%s'.",fn);
  167. // seek to the end
  168. if( fseek(fp,0,SEEK_END) != 0 )
  169. {
  170. rc= _cmCsvError(p,kFileSeekErrCsvRC,"Unable to seek to the end of '%s'.",fn);
  171. goto errLabel;
  172. }
  173. // get the length of the file
  174. if( (n=ftell(fp)) == 0 )
  175. {
  176. rc = _cmCsvError(p,kFileOpenErrCsvRC,"The file '%s' appears to be empty.",fn);
  177. goto errLabel;
  178. }
  179. // rewind the file
  180. if( fseek(fp,0,SEEK_SET) != 0 )
  181. {
  182. rc = _cmCsvError(p,kFileSeekErrCsvRC,"Unable to seek to the beginning of '%s'.",fn);
  183. goto errLabel;
  184. }
  185. // allocate the text buffer
  186. if((textBuf = cmMemAllocZ( char, n+1)) == NULL )
  187. {
  188. rc = _cmCsvError(p,kMemAllocErrCsvRC,"Unable to allocate the text file buffer for:'%s'.",fn);
  189. goto errLabel;
  190. }
  191. // read the file into the text buffer
  192. if( fread(textBuf,n,1,fp) != 1 )
  193. {
  194. rc = _cmCsvError(p,kFileReadErrCsvRC,"File read failed on:'%s'.",fn);
  195. goto errLabel;
  196. }
  197. rc = cmCsvParse(h,textBuf,n,maxRowCnt);
  198. errLabel:
  199. // close the file
  200. if( fclose(fp) != 0 )
  201. {
  202. rc = _cmCsvError(p,kFileCloseErrCsvRC,"File close failed on:'%s'.",fn);
  203. goto errLabel;
  204. }
  205. // free the buffer
  206. if( textBuf != NULL )
  207. cmMemFree(textBuf);
  208. return rc;
  209. }
  210. cmCsvRC_t _cmCsvAppendNewBindRecd( cmCsv_t* p, cmCsvBind_t** newBindPtrPtr, unsigned* newRowIdxPtr )
  211. {
  212. cmCsvBind_t* nbp;
  213. cmCsvBind_t* bp = p->bindPtr;
  214. unsigned newRowIdx = 0;
  215. if( newBindPtrPtr != NULL )
  216. *newBindPtrPtr = NULL;
  217. if( newRowIdxPtr != NULL )
  218. *newRowIdxPtr = cmInvalidIdx;
  219. // allocate the binder record
  220. if((nbp = cmLHeapAllocZ( p->heapH, sizeof(cmCsvBind_t))) == NULL )
  221. return _cmCsvError(p,kMemAllocErrCsvRC,"Binding record allocation failed.");
  222. // if this is the first binder record
  223. if( p->bindPtr == NULL )
  224. {
  225. p->bindPtr = nbp;
  226. bp = nbp;
  227. }
  228. else
  229. {
  230. newRowIdx = 1;
  231. // iterate to the bottom of the binding
  232. while( bp->linkPtr != NULL )
  233. {
  234. bp = bp->linkPtr;
  235. ++newRowIdx;
  236. }
  237. bp->linkPtr = nbp;
  238. }
  239. if( newBindPtrPtr != NULL )
  240. *newBindPtrPtr = nbp;
  241. if( newRowIdxPtr != NULL )
  242. *newRowIdxPtr = newRowIdx;
  243. return kOkCsvRC;
  244. }
  245. cmCsvRC_t _cmCsvCreateBind( cmCsv_t* p, cmCsvCell_t* cp, const char* tokenText, unsigned lexRow, unsigned lexCol )
  246. {
  247. cmCsvRC_t rc;
  248. cmCsvBind_t* nbp;
  249. if((rc = _cmCsvAppendNewBindRecd(p,&nbp,NULL)) != kOkCsvRC )
  250. return _cmCsvError(p,kMemAllocErrCsvRC,"Binding record allocation failed for '%s' on line %i column %i.",tokenText,lexRow,lexCol);
  251. nbp->rowPtr = cp;
  252. return rc;
  253. }
  254. cmCsvRC_t _cmCsvAllocCell( cmCsv_t* p, unsigned symId, unsigned flags, unsigned cellRow, unsigned cellCol, cmCsvCell_t** cpp, unsigned lexTId )
  255. {
  256. cmCsvCell_t* cp;
  257. // allocate cell memory
  258. if(( cp = cmLHeapAllocZ(p->heapH, sizeof(cmCsvCell_t) )) == NULL )
  259. return _cmCsvError(p,kMemAllocErrCsvRC,"Cell allocation failed. for row: %i column %i.",cellRow+1,cellCol+1);
  260. cp->row = cellRow;
  261. cp->col = cellCol;
  262. cp->symId = symId;
  263. cp->flags = flags;
  264. cp->lexTId= lexTId;
  265. *cpp = cp;
  266. return kOkCsvRC;
  267. }
  268. cmCsvRC_t _cmCsvCreateCell( cmCsv_t* p, const char* tokenText, unsigned flags, unsigned cellRow, unsigned cellCol, unsigned lexTId )
  269. {
  270. unsigned symId = cmInvalidId;
  271. cmCsvCell_t* cp = NULL;
  272. unsigned lexRow = cmLexCurrentLineNumber(p->lexH);
  273. unsigned lexCol = cmLexCurrentColumnNumber(p->lexH);
  274. cmCsvRC_t rc = kOkCsvRC;
  275. // register the token text as a symbol
  276. if((symId = cmHashTblStoreStr(p->htH,tokenText)) == cmInvalidId )
  277. return _cmCsvError(p,kHashTblErrCsvRC,"Symbol registration failed. for '%s' on line %i column %i.",tokenText,lexRow,lexCol);
  278. // allocate a cell
  279. if((rc = _cmCsvAllocCell(p,symId,flags,cellRow,cellCol,&cp,lexTId)) != kOkCsvRC )
  280. return rc;
  281. // if this is the first cell in the row
  282. if( p->curRowPtr == NULL )
  283. {
  284. // link in a new binding record
  285. if((rc = _cmCsvCreateBind( p, cp,tokenText,lexRow,lexCol)) != kOkCsvRC )
  286. return rc; // this return will result in a leak from the lheap but it is a fatal error so ignore it
  287. // update the current row ptr
  288. p->curRowPtr = cp;
  289. }
  290. else
  291. {
  292. // iterate to the end of the row
  293. cmCsvCell_t* rp = p->curRowPtr;
  294. while( rp->rowPtr != NULL )
  295. rp = rp->rowPtr;
  296. rp->rowPtr = cp;
  297. }
  298. return rc;
  299. }
  300. const cmCsvUdef_t* _cmCsvFindUdef( cmCsv_t* p, unsigned id )
  301. {
  302. const cmCsvUdef_t* u = p->udefList;
  303. while( u != NULL )
  304. {
  305. if( u->id == id )
  306. return u;
  307. u = u->linkPtr;
  308. }
  309. return NULL;
  310. }
  311. cmCsvRC_t _cmCsvRegUdef( cmCsv_t* p, unsigned id )
  312. {
  313. if( _cmCsvFindUdef(p,id) != NULL )
  314. return _cmCsvError(p,kDuplicateLexCsvId,"%i has already been used as a user defined id.",id);
  315. cmCsvUdef_t* u = cmLhAllocZ(p->heapH,cmCsvUdef_t,1);
  316. u->id = id;
  317. u->linkPtr = p->udefList;
  318. p->udefList = u;
  319. return kOkCsvRC;
  320. }
  321. cmCsvRC_t cmCsvLexRegisterToken( cmCsvH_t h, unsigned id, const cmChar_t* token )
  322. {
  323. cmCsv_t* p = _cmCsvHandleToPtr(h);
  324. if(cmLexRegisterToken(p->lexH,id,token) != kOkLexRC )
  325. return _cmCsvError(p, kLexErrCsvRC,"Error registering user defined token '%s'.",cmStringNullGuard(token));
  326. return _cmCsvRegUdef(p,id);
  327. }
  328. cmCsvRC_t cmCsvLexRegisterMatcher( cmCsvH_t h, unsigned id, cmLexUserMatcherPtr_t matchFunc )
  329. {
  330. cmCsv_t* p = _cmCsvHandleToPtr(h);
  331. if( cmLexRegisterMatcher(p->lexH,id,matchFunc) != kOkLexRC )
  332. return _cmCsvError(p, kLexErrCsvRC,"Error registering user defined matching function.");
  333. return _cmCsvRegUdef(p,id);
  334. }
  335. unsigned cmCsvLexNextAvailId( cmCsvH_t h )
  336. { return kMaxLexTId; }
  337. cmCsvRC_t cmCsvParse( cmCsvH_t h, const char* buf, unsigned bufCharCnt, unsigned maxRowCnt )
  338. {
  339. cmCsvRC_t rc = kOkCsvRC;
  340. unsigned prvLexRow = 1;
  341. unsigned csvRowIdx = 0;
  342. unsigned csvColIdx = 0;
  343. unsigned lexTId = kErrorLexTId; // cur lex token type id
  344. cmCsv_t* p = _cmCsvHandleToPtr(h);
  345. // assign the text buffer and reset the lexer
  346. if((rc = cmLexSetTextBuffer( p->lexH, buf, bufCharCnt )) != kOkLexRC )
  347. return _cmCsvError( p, kLexErrCsvRC, "Error setting lexer buffer.\nLexer Error:%s",cmLexRcToMsg(rc));
  348. // get the next token
  349. while( (lexTId = cmLexGetNextToken( p->lexH )) != kErrorLexTId && (lexTId != kEofLexTId ) && (rc==kOkCsvRC) && (maxRowCnt==0 || csvRowIdx<maxRowCnt))
  350. {
  351. unsigned flags = 0;
  352. unsigned curLexRow = cmLexCurrentLineNumber(p->lexH);
  353. // if we are starting a new row
  354. if( curLexRow != prvLexRow )
  355. {
  356. prvLexRow = curLexRow;
  357. ++csvRowIdx;
  358. csvColIdx = 0;
  359. p->curRowPtr = NULL; // force a new binding record
  360. }
  361. // copy the token text in a buffer
  362. unsigned n = cmLexTokenCharCount(p->lexH);
  363. char buf[n+1];
  364. strncpy(buf, cmLexTokenText(p->lexH), n );
  365. buf[n]=0;
  366. // do cell data type specific processing
  367. switch( lexTId )
  368. {
  369. case kCommaLexTId:
  370. ++csvColIdx;
  371. break;
  372. case kRealLexTId: flags = kRealCsvTFl; break;
  373. case kIntLexTId: flags = kIntCsvTFl; break;
  374. case kHexLexTId: flags = kHexCsvTFl; break;
  375. case kIdentLexTId:flags = kIdentCsvTFl; break;
  376. case kQStrLexTId: flags = kStrCsvTFl; break;
  377. default:
  378. {
  379. const cmCsvUdef_t* u;
  380. if((u = _cmCsvFindUdef(p,lexTId)) == NULL )
  381. rc = _cmCsvError(p, kSyntaxErrCsvRC, "Unrecognized token type: '%s' for token: '%s' row:%i col:%i.",cmLexIdToLabel(p->lexH,lexTId),buf,csvRowIdx+1,csvColIdx+1);
  382. else
  383. flags = kStrCsvTFl | kUdefCsvTFl;
  384. }
  385. }
  386. // if no error occurred and the token is not a comma and the cell is not empty
  387. if( rc == kOkCsvRC && lexTId != kCommaLexTId && strlen(buf) > 0 )
  388. if((rc = _cmCsvCreateCell( p, buf,flags, csvRowIdx, csvColIdx, lexTId )) != kOkCsvRC )
  389. rc = _cmCsvError(p,rc,"CSV cell create fail on row:%i col:%i\n",csvRowIdx+1,csvColIdx+1);
  390. }
  391. if( lexTId == kErrorLexTId )
  392. rc = _cmCsvError(p, kSyntaxErrCsvRC,"The lexer encountered an unrecognized token row:%i col:%i.",csvRowIdx+1,csvColIdx+1);
  393. return rc;
  394. }
  395. unsigned cmCsvRowCount( cmCsvH_t h )
  396. {
  397. cmCsv_t* p = _cmCsvHandleToPtr(h);
  398. unsigned rowCnt = 0;
  399. cmCsvBind_t* bp = p->bindPtr;
  400. for(; bp != NULL; ++rowCnt )
  401. bp = bp->linkPtr;
  402. return rowCnt;
  403. }
  404. cmCsvCell_t* cmCsvRowPtr( cmCsvH_t h, unsigned row )
  405. {
  406. cmCsv_t* p = _cmCsvHandleToPtr(h);
  407. cmCsvBind_t* bp = p->bindPtr;
  408. while( bp != NULL )
  409. {
  410. // note: bp->rowPtr of blank rows will be NULL
  411. if( bp->rowPtr!=NULL && bp->rowPtr->row == row )
  412. return bp->rowPtr;
  413. bp = bp->linkPtr;
  414. }
  415. return NULL;
  416. }
  417. cmCsvCell_t* cmCsvCellPtr( cmCsvH_t h, unsigned row, unsigned col )
  418. {
  419. cmCsvCell_t* cp;
  420. if((cp = cmCsvRowPtr(h,row)) == NULL )
  421. return NULL;
  422. while( cp != NULL )
  423. {
  424. if( cp->col == col )
  425. return cp;
  426. cp = cp->rowPtr;
  427. }
  428. return NULL;
  429. }
  430. cmCsvCell_t* _cmCsvCellPtr( cmCsvH_t h, unsigned row, unsigned col )
  431. {
  432. cmCsvCell_t* cp;
  433. if((cp = cmCsvCellPtr(h,row,col)) == NULL )
  434. _cmCsvError(_cmCsvHandleToPtr(h),kCellNotFoundCsvRC,"Cell at row:%i col:%i not found.",row,col);
  435. return cp;
  436. }
  437. const char* cmCsvCellSymText( cmCsvH_t h, unsigned symId )
  438. {
  439. cmCsv_t* p = _cmCsvHandleToPtr(h);
  440. const char* cp;
  441. if((cp = cmHashTblStr(p->htH,symId)) == NULL )
  442. _cmCsvError(p,kHashTblErrCsvRC,"The text associated with the symbol '%i' was not found.",symId);
  443. return cp;
  444. }
  445. cmCsvRC_t cmCsvCellSymInt( cmCsvH_t h, unsigned symId, int* vp )
  446. {
  447. const char* cp;
  448. cmCsv_t* p = _cmCsvHandleToPtr(h);
  449. if((cp = cmCsvCellSymText(h,symId)) == NULL )
  450. return kHashTblErrCsvRC;
  451. if( cmTextToInt(cp,vp,&p->err) != kOkTxRC )
  452. return _cmCsvError(p,kDataCvtErrCsvRC,"CSV text to int value failed.");
  453. return kOkCsvRC;
  454. }
  455. cmCsvRC_t cmCsvCellSymUInt( cmCsvH_t h, unsigned symId, unsigned* vp )
  456. {
  457. const char* cp;
  458. cmCsv_t* p = _cmCsvHandleToPtr(h);
  459. if((cp = cmCsvCellSymText(h,symId)) == NULL )
  460. return kHashTblErrCsvRC;
  461. if( cmTextToUInt(cp,vp,&p->err) != kOkTxRC )
  462. return _cmCsvError(p,kDataCvtErrCsvRC,"CSV text to uint value failed.");
  463. return kOkCsvRC;
  464. }
  465. cmCsvRC_t cmCsvCellSymFloat( cmCsvH_t h, unsigned symId, float* vp )
  466. {
  467. const char* cp;
  468. cmCsv_t* p = _cmCsvHandleToPtr(h);
  469. if((cp = cmCsvCellSymText(h,symId)) == NULL )
  470. return kHashTblErrCsvRC;
  471. if( cmTextToFloat(cp,vp,&p->err) != kOkTxRC )
  472. return _cmCsvError(p,kDataCvtErrCsvRC,"CSV text to float value failed.");
  473. return kOkCsvRC;
  474. }
  475. cmCsvRC_t cmCsvCellSymDouble( cmCsvH_t h, unsigned symId, double* vp )
  476. {
  477. const char* cp;
  478. cmCsv_t* p = _cmCsvHandleToPtr(h);
  479. if((cp = cmCsvCellSymText(h,symId)) == NULL )
  480. return kHashTblErrCsvRC;
  481. if( cmTextToDouble(cp,vp,&p->err) != kOkTxRC )
  482. return _cmCsvError(p,kDataCvtErrCsvRC,"CSV text to double value failed.");
  483. return kOkCsvRC;
  484. }
  485. const char* cmCsvCellText( cmCsvH_t h, unsigned row, unsigned col )
  486. {
  487. cmCsvCell_t* cp;
  488. if((cp = cmCsvCellPtr(h,row,col)) == NULL )
  489. return NULL;
  490. return cmCsvCellSymText(h,cp->symId);
  491. }
  492. int cmCsvCellInt( cmCsvH_t h, unsigned row, unsigned col )
  493. {
  494. cmCsvCell_t* cp;
  495. int v;
  496. if((cp = cmCsvCellPtr(h,row,col)) == NULL )
  497. return INT_MAX;
  498. if(cmCsvCellSymInt(h,cp->symId,&v) != kOkCsvRC )
  499. return INT_MAX;
  500. return v;
  501. }
  502. unsigned cmCsvCellUInt( cmCsvH_t h, unsigned row, unsigned col )
  503. {
  504. cmCsvCell_t* cp;
  505. unsigned v;
  506. if((cp = cmCsvCellPtr(h,row,col)) == NULL )
  507. return UINT_MAX;
  508. if(cmCsvCellSymUInt(h,cp->symId,&v) != kOkCsvRC )
  509. return UINT_MAX;
  510. return v;
  511. }
  512. float cmCsvCellFloat( cmCsvH_t h, unsigned row, unsigned col )
  513. {
  514. cmCsvCell_t* cp;
  515. float v;
  516. if((cp = cmCsvCellPtr(h,row,col)) == NULL )
  517. return FLT_MAX;
  518. if(cmCsvCellSymFloat(h,cp->symId,&v) != kOkCsvRC )
  519. return FLT_MAX;
  520. return v;
  521. }
  522. double cmCsvCellDouble(cmCsvH_t h, unsigned row, unsigned col )
  523. {
  524. cmCsvCell_t* cp;
  525. double v;
  526. if((cp = cmCsvCellPtr(h,row,col)) == NULL )
  527. return DBL_MAX;
  528. if(cmCsvCellSymDouble(h,cp->symId,&v) != kOkCsvRC )
  529. return DBL_MAX;
  530. return v;
  531. }
  532. unsigned cmCsvInsertSymText( cmCsvH_t h, const char* text )
  533. {
  534. cmCsv_t* p = _cmCsvHandleToPtr(h);
  535. unsigned symId;
  536. if((symId = cmHashTblStoreStr(p->htH,text)) == cmInvalidId )
  537. _cmCsvError(p,kHashTblErrCsvRC,"'%s' could not be inserted into the symbol table.",text);
  538. return symId;
  539. }
  540. unsigned cmCsvInsertSymInt( cmCsvH_t h, int v )
  541. {
  542. const char* fmt = "%i";
  543. unsigned n = snprintf(NULL,0,fmt,v)+1;
  544. char buf[n];
  545. buf[0]= 0;
  546. if( snprintf(buf,n,fmt,v) == n-1 )
  547. return cmCsvInsertSymText(h,buf);
  548. _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The integer %i could not be converted to text.",v);
  549. return cmInvalidId;
  550. }
  551. unsigned cmCsvInsertSymUInt( cmCsvH_t h, unsigned v )
  552. {
  553. const char* fmt = "%i";
  554. unsigned n = snprintf(NULL,0,fmt,v)+1;
  555. char buf[n];
  556. buf[0]= 0;
  557. if( snprintf(buf,n,fmt,v) == n-1 )
  558. return cmCsvInsertSymText(h,buf);
  559. _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The unsigned int %i could not be converted to text.",v);
  560. return cmInvalidId;
  561. }
  562. unsigned cmCsvInsertSymHex( cmCsvH_t h, unsigned v )
  563. {
  564. const char* fmt = "0x%x";
  565. unsigned n = snprintf(NULL,0,fmt,v)+1;
  566. char buf[n];
  567. buf[0]= 0;
  568. if( snprintf(buf,n,fmt,v) == n-1 )
  569. return cmCsvInsertSymText(h,buf);
  570. _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The unsigned int 0x%x could not be converted to text.",v);
  571. return cmInvalidId;
  572. }
  573. unsigned cmCsvInsertSymFloat( cmCsvH_t h, float v )
  574. {
  575. const char* fmt = "%f";
  576. unsigned n = snprintf(NULL,0,fmt,v)+1;
  577. char buf[n];
  578. buf[0] = 0;
  579. if( snprintf(buf,n,fmt,v) == n-1 )
  580. return cmCsvInsertSymText(h,buf);
  581. _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The float %f could not be converted to text.",v);
  582. return cmInvalidId;
  583. }
  584. unsigned cmCsvInsertSymDouble( cmCsvH_t h, double v )
  585. {
  586. const char* fmt = "%f";
  587. unsigned n = snprintf(NULL,0,fmt,v)+1;
  588. char buf[n];
  589. buf[0]= 0;
  590. if( snprintf(buf,n,fmt,v) == n-1 )
  591. return cmCsvInsertSymText(h,buf);
  592. _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The double %f could not be converted to text.",v);
  593. return cmInvalidId;
  594. }
  595. cmCsvRC_t cmCsvSetCellIdent( cmCsvH_t h, unsigned row, unsigned col, const char* text )
  596. {
  597. cmCsvCell_t* cp;
  598. unsigned symId;
  599. if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
  600. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  601. if((symId = cmCsvInsertSymText(h,text)) == cmInvalidId )
  602. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  603. cp->symId = symId;
  604. cp->flags &= !kTypeTMask;
  605. cp->flags |= kIdentCsvTFl;
  606. return kOkCsvRC;
  607. }
  608. cmCsvRC_t cmCsvSetCellText( cmCsvH_t h, unsigned row, unsigned col, const char* text )
  609. {
  610. cmCsvCell_t* cp;
  611. unsigned symId;
  612. if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
  613. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  614. if((symId = cmCsvInsertSymText(h,text)) == cmInvalidId )
  615. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  616. cp->symId = symId;
  617. cp->flags &= !kTypeTMask;
  618. cp->flags |= kStrCsvTFl;
  619. return kOkCsvRC;
  620. }
  621. cmCsvRC_t cmCsvSetCellInt( cmCsvH_t h, unsigned row, unsigned col, int v )
  622. {
  623. cmCsvCell_t* cp;
  624. unsigned symId;
  625. if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
  626. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  627. if((symId = cmCsvInsertSymInt(h,v)) == cmInvalidId )
  628. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  629. cp->symId = symId;
  630. cp->flags &= !kTypeTMask;
  631. cp->flags |= kIntCsvTFl;
  632. return kOkCsvRC;
  633. }
  634. cmCsvRC_t cmCsvSetCellUInt( cmCsvH_t h, unsigned row, unsigned col, unsigned v )
  635. {
  636. cmCsvCell_t* cp;
  637. unsigned symId;
  638. if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
  639. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  640. if((symId = cmCsvInsertSymUInt(h,v)) == cmInvalidId )
  641. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  642. cp->symId = symId;
  643. cp->flags &= !kTypeTMask;
  644. cp->flags |= kIntCsvTFl;
  645. return kOkCsvRC;
  646. }
  647. cmCsvRC_t cmCsvSetCellHex( cmCsvH_t h, unsigned row, unsigned col, unsigned v )
  648. {
  649. cmCsvCell_t* cp;
  650. unsigned symId;
  651. if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
  652. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  653. if((symId = cmCsvInsertSymHex(h,v)) == cmInvalidId )
  654. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  655. cp->symId = symId;
  656. cp->flags &= !kTypeTMask;
  657. cp->flags |= kIntCsvTFl;
  658. return kOkCsvRC;
  659. }
  660. cmCsvRC_t cmCsvSetCellFloat( cmCsvH_t h, unsigned row, unsigned col, float v )
  661. {
  662. cmCsvCell_t* cp;
  663. unsigned symId;
  664. if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
  665. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  666. if((symId = cmCsvInsertSymFloat(h,v)) == cmInvalidId )
  667. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  668. cp->symId = symId;
  669. cp->flags &= !kTypeTMask;
  670. cp->flags |= kRealCsvTFl;
  671. return kOkCsvRC;
  672. }
  673. cmCsvRC_t cmCsvSetCellDouble( cmCsvH_t h, unsigned row, unsigned col, double v )
  674. {
  675. cmCsvCell_t* cp;
  676. unsigned symId;
  677. if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
  678. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  679. if((symId = cmCsvInsertSymDouble(h,v)) == cmInvalidId )
  680. return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  681. cp->symId = symId;
  682. cp->flags &= !kTypeTMask;
  683. cp->flags |= kRealCsvTFl;
  684. return kOkCsvRC;
  685. }
  686. cmCsvRC_t cmCsvInsertRowBefore( cmCsvH_t h, unsigned row, cmCsvCell_t** cellPtrPtr, unsigned symId, unsigned flags, unsigned lexTId )
  687. {
  688. cmCsvRC_t rc = kOkCsvRC;
  689. cmCsv_t* p = _cmCsvHandleToPtr(h);
  690. cmCsvBind_t* bp = p->bindPtr;
  691. cmCsvBind_t* nbp = NULL;
  692. unsigned i = 0;
  693. if( cellPtrPtr != NULL )
  694. *cellPtrPtr = NULL;
  695. // allocate the binder record
  696. if((nbp = cmLHeapAllocZ( p->heapH, sizeof(cmCsvBind_t))) == NULL )
  697. return _cmCsvError(p,kMemAllocErrCsvRC,"Binding record allocation failed row %i column %i.",row,0);
  698. // if a new first row is being inserted
  699. if( row == 0 )
  700. {
  701. bp = p->bindPtr;
  702. nbp->linkPtr = p->bindPtr;
  703. p->bindPtr = nbp;
  704. }
  705. else
  706. {
  707. bp = p->bindPtr;
  708. // iterate to the row before the new row
  709. for(i=0; bp != NULL; ++i )
  710. {
  711. if( i == (row-1) || bp->linkPtr == NULL )
  712. break;
  713. bp = bp->linkPtr;
  714. }
  715. assert( bp != NULL );
  716. nbp->linkPtr = bp->linkPtr;
  717. bp->linkPtr = nbp;
  718. bp = bp->linkPtr;
  719. }
  720. // update the row numbers in all cells below the new row
  721. while( bp != NULL )
  722. {
  723. cmCsvCell_t* cp = bp->rowPtr;
  724. while( cp != NULL )
  725. {
  726. cp->row += 1;
  727. cp = cp->rowPtr;
  728. }
  729. bp = bp->linkPtr;
  730. }
  731. // allocate the first cell in the new row
  732. if(cellPtrPtr != NULL && symId != cmInvalidId )
  733. {
  734. if((rc = _cmCsvAllocCell(p, symId, flags, row, 0, cellPtrPtr, lexTId )) != kOkCsvRC )
  735. return rc;
  736. nbp->rowPtr = *cellPtrPtr;
  737. }
  738. return rc;
  739. }
  740. cmCsvRC_t cmCsvAppendRow( cmCsvH_t h, cmCsvCell_t** cellPtrPtr, unsigned symId, unsigned flags, unsigned lexTId )
  741. {
  742. cmCsvRC_t rc = kOkCsvRC;
  743. cmCsv_t* p = _cmCsvHandleToPtr(h);
  744. cmCsvBind_t* nbp = NULL;
  745. unsigned newRowIdx = cmInvalidIdx;
  746. if( cellPtrPtr != NULL )
  747. *cellPtrPtr = NULL;
  748. if((rc = _cmCsvAppendNewBindRecd(p,&nbp,&newRowIdx)) != kOkCsvRC )
  749. return rc;
  750. // allocate the first cell in the new row
  751. if(cellPtrPtr != NULL && symId != cmInvalidId )
  752. {
  753. if((rc = _cmCsvAllocCell(p, symId, flags, newRowIdx, 0, cellPtrPtr, lexTId )) != kOkCsvRC )
  754. return rc;
  755. nbp->rowPtr = *cellPtrPtr;
  756. }
  757. return rc;
  758. }
  759. cmCsvRC_t cmCsvInsertColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, unsigned symId, unsigned flags, unsigned lexTId )
  760. {
  761. cmCsvRC_t rc = kOkCsvRC;
  762. cmCsvCell_t* ncp = NULL;
  763. cmCsvCell_t* cp = NULL;
  764. cmCsv_t* p = _cmCsvHandleToPtr(h);
  765. unsigned col;
  766. if(cellPtrPtr != NULL )
  767. *cellPtrPtr = NULL;
  768. // allocate the new cell
  769. if((rc = _cmCsvAllocCell(p, symId, flags, leftCellPtr->row, leftCellPtr->col+1, &ncp, lexTId )) != kOkCsvRC )
  770. return rc;
  771. // update the col values of cells to right of new cell
  772. cp = leftCellPtr->rowPtr;
  773. col = leftCellPtr->col + 1;
  774. for(; cp != NULL; ++col )
  775. {
  776. // don't update any col numbers after a blank (missing) column
  777. if( cp->col == col )
  778. cp->col += 1;
  779. cp = cp->rowPtr;
  780. }
  781. // link in the new cell
  782. ncp->rowPtr = leftCellPtr->rowPtr;
  783. leftCellPtr->rowPtr = ncp;
  784. if(cellPtrPtr != NULL )
  785. *cellPtrPtr = ncp;
  786. return rc;
  787. }
  788. cmCsvRC_t cmCsvInsertIdentColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, const char* text, unsigned lexTId )
  789. {
  790. cmCsvRC_t rc;
  791. cmCsvCell_t* ncp;
  792. if( cellPtrPtr != NULL )
  793. *cellPtrPtr = NULL;
  794. if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
  795. if((rc = cmCsvSetCellIdent(h, ncp->row, ncp->col, text )) == kOkCsvRC )
  796. if( cellPtrPtr != NULL )
  797. *cellPtrPtr = ncp;
  798. return rc;
  799. }
  800. cmCsvRC_t cmCsvInsertQTextColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, const char* text, unsigned lexTId )
  801. {
  802. cmCsvRC_t rc;
  803. cmCsvCell_t* ncp;
  804. if( cellPtrPtr != NULL )
  805. *cellPtrPtr = NULL;
  806. if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
  807. if((rc = cmCsvSetCellText(h, ncp->row, ncp->col, text )) == kOkCsvRC )
  808. if( cellPtrPtr != NULL )
  809. *cellPtrPtr = ncp;
  810. return rc;
  811. }
  812. cmCsvRC_t cmCsvInsertIntColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, int val, unsigned lexTId )
  813. {
  814. cmCsvRC_t rc;
  815. cmCsvCell_t* ncp;
  816. if( cellPtrPtr != NULL )
  817. *cellPtrPtr = NULL;
  818. if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
  819. if((rc = cmCsvSetCellInt(h, ncp->row, ncp->col, val )) == kOkCsvRC )
  820. if( cellPtrPtr != NULL )
  821. *cellPtrPtr = ncp;
  822. return rc;
  823. }
  824. cmCsvRC_t cmCsvInsertUIntColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, unsigned val, unsigned lexTId )
  825. {
  826. cmCsvRC_t rc;
  827. cmCsvCell_t* ncp;
  828. if( cellPtrPtr != NULL )
  829. *cellPtrPtr = NULL;
  830. if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
  831. if((rc = cmCsvSetCellUInt(h, ncp->row, ncp->col, val )) == kOkCsvRC )
  832. if( cellPtrPtr != NULL )
  833. *cellPtrPtr = ncp;
  834. return rc;
  835. }
  836. cmCsvRC_t cmCsvInsertHexColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, unsigned val, unsigned lexTId )
  837. {
  838. cmCsvRC_t rc;
  839. cmCsvCell_t* ncp;
  840. if( cellPtrPtr != NULL )
  841. *cellPtrPtr = NULL;
  842. if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
  843. if((rc = cmCsvSetCellHex(h, ncp->row, ncp->col, val )) == kOkCsvRC )
  844. if( cellPtrPtr != NULL )
  845. *cellPtrPtr = ncp;
  846. return rc;
  847. }
  848. cmCsvRC_t cmCsvInsertFloatColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, float val, unsigned lexTId )
  849. {
  850. cmCsvRC_t rc;
  851. cmCsvCell_t* ncp;
  852. if( cellPtrPtr != NULL )
  853. *cellPtrPtr = NULL;
  854. if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
  855. if((rc = cmCsvSetCellFloat(h, ncp->row, ncp->col, val )) == kOkCsvRC )
  856. if( cellPtrPtr != NULL )
  857. *cellPtrPtr = ncp;
  858. return rc;
  859. }
  860. cmCsvRC_t cmCsvInsertDoubleColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, double val, unsigned lexTId )
  861. {
  862. cmCsvRC_t rc;
  863. cmCsvCell_t* ncp;
  864. if( cellPtrPtr != NULL )
  865. *cellPtrPtr = NULL;
  866. if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
  867. if((rc = cmCsvSetCellDouble(h, ncp->row, ncp->col, val )) == kOkCsvRC )
  868. if( cellPtrPtr != NULL )
  869. *cellPtrPtr = ncp;
  870. return rc;
  871. }
  872. cmCsvRC_t cmCsvWrite( cmCsvH_t h, const char* fn )
  873. {
  874. FILE* fp = NULL;
  875. cmCsvRC_t rc = kOkCsvRC;
  876. cmCsv_t* p = _cmCsvHandleToPtr(h);
  877. cmCsvBind_t* bp = p->bindPtr;
  878. if((fp = fopen(fn,"wt")) == NULL )
  879. return _cmCsvError(p,kFileCreateErrCsvRC,"Unable to create the output CSV file:'%s'.",fn);
  880. bp = p->bindPtr;
  881. // for each row
  882. while( bp != NULL )
  883. {
  884. cmCsvCell_t* cp = bp->rowPtr;
  885. unsigned col = 0;
  886. // for each column
  887. for(; cp != NULL; ++col )
  888. {
  889. // skip blank columns
  890. if( cp->col == col )
  891. {
  892. const char* tp;
  893. if((tp = cmHashTblStr(p->htH,cp->symId)) == NULL )
  894. return _cmCsvError(p,kHashTblErrCsvRC,"Unable to locate the symbol text for cell at row:%i col:%i.",cp->row,cp->col);
  895. if( cmIsFlag(cp->flags,kStrCsvTFl) )
  896. fprintf(fp,"\"");
  897. fputs(tp,fp);
  898. if( cmIsFlag(cp->flags,kStrCsvTFl) )
  899. fprintf(fp,"\"");
  900. cp = cp->rowPtr;
  901. }
  902. if( cp == NULL )
  903. fprintf(fp,"\n"); // end of row
  904. else
  905. fprintf(fp,","); // between columns
  906. }
  907. bp = bp->linkPtr;
  908. }
  909. fclose(fp);
  910. return rc;
  911. }
  912. cmCsvRC_t cmCsvPrint( cmCsvH_t h, unsigned rowCnt )
  913. {
  914. cmCsv_t* p = _cmCsvHandleToPtr(h);
  915. cmCsvBind_t* bp = p->bindPtr;
  916. unsigned i;
  917. for(i=0; bp!=NULL && i<rowCnt; ++i,bp=bp->linkPtr)
  918. {
  919. cmCsvCell_t* cp = bp->rowPtr;
  920. unsigned j;
  921. for(j=0; cp!=NULL; ++j)
  922. {
  923. if( cp->col == j )
  924. {
  925. const char* tp;
  926. if((tp = cmHashTblStr(p->htH,cp->symId)) == NULL )
  927. _cmCsvError(p,kHashTblErrCsvRC,"The text associated with the symbol '%i' was not found.",cp->symId);
  928. fputs(tp,stdin);
  929. }
  930. cp=cp->rowPtr;
  931. if( cp == NULL )
  932. printf("\n");
  933. else
  934. printf(",");
  935. }
  936. }
  937. return kOkCsvRC;
  938. }