msexceltables.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. /* msexceltables.c Steve Simon 5-Jan-2005 */
  2. #include <u.h>
  3. #include <libc.h>
  4. #include <bio.h>
  5. #include <ctype.h>
  6. enum {
  7. Tillegal = 0,
  8. Tnumber, // cell types
  9. Tlabel,
  10. Tindex,
  11. Tbool,
  12. Terror,
  13. Ver8 = 0x600, // only BIFF8 and BIFF8x files support unicode
  14. Nwidths = 4096,
  15. };
  16. typedef struct Biff Biff;
  17. typedef struct Col Col;
  18. typedef struct Row Row;
  19. struct Row {
  20. Row *next; // next row
  21. int r; // row number
  22. Col *col; // list of cols in row
  23. };
  24. struct Col {
  25. Col *next; // next col in row
  26. int c; // col number
  27. int f; // index into formating table (Xf)
  28. int type; // type of value for union below
  29. union { // value
  30. int index; // index into string table (Strtab)
  31. int error;
  32. int bool;
  33. char *label;
  34. double number;
  35. };
  36. };
  37. struct Biff {
  38. Biobuf *bp; // input file
  39. int op; // current record type
  40. int len; // length of current record
  41. };
  42. // options
  43. static int Nopad = 0; // disable padding cells to colum width
  44. static int Trunc = 0; // truncate cells to colum width
  45. static int All = 0; // dump all sheet types, Worksheets only by default
  46. static char *Delim = " "; // field delimiter
  47. static char *Sheetrange = nil; // range of sheets wanted
  48. static int Debug = 0;
  49. // file scope
  50. static int Defwidth = 10; // default colum width if non given
  51. static int Biffver; // file vesion
  52. static int Datemode; // date ref: 1899-Dec-31 or 1904-jan-1
  53. static char **Strtab = nil; // label contents heap
  54. static int Nstrtab = 0; // # of above
  55. static int *Xf; // array of extended format indices
  56. static int Nxf = 0; // # of above
  57. static Biobuf *bo; // stdout (sic)
  58. // table scope
  59. static int Width[Nwidths]; // array of colum widths
  60. static int Ncols = -1; // max colums in table used
  61. static int Content = 0; // type code for contents of sheet
  62. static Row *Root = nil; // one worksheet's worth of cells
  63. static char *Months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  64. "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
  65. static char *Errmsgs[] = {
  66. [0x0] "#NULL!", // intersection of two cell ranges is empty
  67. [0x7] "#DIV/0!", // division by zero
  68. [0xf] "#VALUE!", // wrong type of operand
  69. [0x17] "#REF!", // illegal or deleted cell reference
  70. [0x1d] "#NAME?", // wrong function or range name
  71. [0x24] "#NUM!", // value range overflow
  72. [0x2a] "#N/A!", // argument of function not available
  73. };
  74. void
  75. cell(int r, int c, int f, int type, void *val)
  76. {
  77. Row *row, *nrow;
  78. Col *col, *ncol;
  79. if(c > Ncols)
  80. Ncols = c;
  81. if((ncol = malloc(sizeof(Col))) == nil)
  82. sysfatal("no memory");
  83. ncol->c = c;
  84. ncol->f = f;
  85. ncol->type = type;
  86. ncol->next = nil;
  87. switch(type){
  88. case Tnumber: ncol->number = *(double *)val; break;
  89. case Tlabel: ncol->label = (char *)val; break;
  90. case Tindex: ncol->index = *(int *)val; break;
  91. case Tbool: ncol->bool = *(int *)val; break;
  92. case Terror: ncol->error = *(int *)val; break;
  93. default: sysfatal("can't happen error");
  94. }
  95. if(Root == nil || Root->r > r){
  96. if((nrow = malloc(sizeof(Row))) == nil)
  97. sysfatal("no memory");
  98. nrow->col = ncol;
  99. ncol->next = nil;
  100. nrow->r = r;
  101. nrow->next = Root;
  102. Root = nrow;
  103. return;
  104. }
  105. for(row = Root; row; row = row->next){
  106. if(row->r == r){
  107. if(row->col->c > c){
  108. ncol->next = row->col;
  109. row->col = ncol;
  110. return;
  111. }
  112. else{
  113. for(col = row->col; col; col = col->next)
  114. if(col->next == nil || col->next->c > c){
  115. ncol->next = col->next;
  116. col->next = ncol;
  117. return;
  118. }
  119. }
  120. }
  121. if(row->next == nil || row->next->r > r){
  122. if((nrow = malloc(sizeof(Row))) == nil)
  123. sysfatal("no memory");
  124. nrow->col = ncol;
  125. nrow->r = r;
  126. nrow->next = row->next;
  127. row->next = nrow;
  128. return;
  129. }
  130. }
  131. sysfatal("cannot happen error");
  132. }
  133. struct Tm *
  134. bifftime(double num)
  135. {
  136. long long t = num;
  137. /* Beware - These epochs are wrong, this
  138. * is due to Excel still remaining compatible
  139. * with Lotus-123, which incorrectly believed 1900
  140. * was a leap year
  141. */
  142. if(Datemode)
  143. t -= 24107; // epoch = 1/1/1904
  144. else
  145. t -= 25569; // epoch = 31/12/1899
  146. t *= 60*60*24;
  147. return localtime((long)t);
  148. }
  149. void
  150. numfmt(int fmt, int min, int max, double num)
  151. {
  152. char buf[1024];
  153. struct Tm *tm;
  154. if(fmt == 9)
  155. snprint(buf, sizeof(buf),"%.0f%%", num);
  156. else
  157. if(fmt == 10)
  158. snprint(buf, sizeof(buf),"%f%%", num);
  159. else
  160. if(fmt == 11 || fmt == 48)
  161. snprint(buf, sizeof(buf),"%e", num);
  162. else
  163. if(fmt >= 14 && fmt <= 17){
  164. tm = bifftime(num);
  165. snprint(buf, sizeof(buf),"%d-%s-%d",
  166. tm->mday, Months[tm->mon], tm->year+1900);
  167. }
  168. else
  169. if((fmt >= 18 && fmt <= 21) || (fmt >= 45 && fmt <= 47)){
  170. tm = bifftime(num);
  171. snprint(buf, sizeof(buf),"%02d:%02d:%02d", tm->hour, tm->min, tm->sec);
  172. }
  173. else
  174. if(fmt == 22){
  175. tm = bifftime(num);
  176. snprint(buf, sizeof(buf),"%02d:%02d:%02d %d-%s-%d",
  177. tm->hour, tm->min, tm->sec,
  178. tm->mday, Months[tm->mon], tm->year+1900);
  179. }else
  180. snprint(buf, sizeof(buf),"%g", num);
  181. Bprint(bo, "%-*.*q", min, max, buf);
  182. }
  183. void
  184. dump(void)
  185. {
  186. Row *r;
  187. Col *c;
  188. int i, min, max;
  189. for(r = Root; r; r = r->next){
  190. for(c = r->col; c; c = c->next){
  191. if(c->c < 0 || c->c >= Nwidths || (min = Width[c->c]) == 0)
  192. min = Defwidth;
  193. if((c->next && c->c == c->next->c) || Nopad)
  194. min = 0;
  195. max = -1;
  196. if(Trunc && min > 2)
  197. max = min -2; // FIXME: -2 because of bug %q format ?
  198. switch(c->type){
  199. case Tnumber:
  200. if(Xf[c->f] == 0)
  201. Bprint(bo, "%-*.*g", min, max, c->number);
  202. else
  203. numfmt(Xf[c->f], min, max, c->number);
  204. break;
  205. case Tlabel:
  206. Bprint(bo, "%-*.*q", min, max, c->label);
  207. break;
  208. case Tbool:
  209. Bprint(bo, "%-*.*s", min, max, (c->bool)? "True": "False");
  210. break;
  211. case Tindex:
  212. if(c->index < 0 || c->index >= Nstrtab)
  213. sysfatal("SST string out of range - corrupt file?");
  214. Bprint(bo, "%-*.*q", min, max, Strtab[c->index]);
  215. break;
  216. case Terror:
  217. if(c->error < 0 || c->error >= nelem(Errmsgs) || !Errmsgs[c->error])
  218. Bprint(bo, "#ERR=%d", c->index);
  219. else
  220. Bprint(bo, "%-*.*q", min, max, Errmsgs[c->error]);
  221. break;
  222. default:
  223. sysfatal("cannot happen error");
  224. break;
  225. }
  226. if(c->next){
  227. if(c->next->c == c->c) // bar charts
  228. Bprint(bo, "=");
  229. else{
  230. Bprint(bo, "%s", Delim);
  231. for(i = c->c; c->next && i < c->next->c -1; i++)
  232. Bprint(bo, "%-*.*s%s", min, max, "", Delim);
  233. }
  234. }
  235. }
  236. if(r->next)
  237. for(i = r->r; i < r->next->r; i++)
  238. Bprint(bo, "\n");
  239. }
  240. Bprint(bo, "\n");
  241. }
  242. void
  243. release(void)
  244. {
  245. Row *r, *or;
  246. Col *c, *oc;
  247. r = Root;
  248. while(r){
  249. c = r->col;
  250. while(c){
  251. if(c->type == Tlabel)
  252. free(c->label);
  253. oc = c;
  254. c = c->next;
  255. free(oc);
  256. }
  257. or = r;
  258. r = r->next;
  259. free(or);
  260. }
  261. Root = nil;
  262. memset(Width, 0, sizeof(Width));
  263. Ncols = -1;
  264. }
  265. void
  266. skip(Biff *b, int len)
  267. {
  268. assert(len <= b->len);
  269. if(Bseek(b->bp, len, 1) == -1)
  270. sysfatal("seek failed - %r");
  271. b->len -= len;
  272. }
  273. void
  274. gmem(Biff *b, void *p, int n)
  275. {
  276. if(b->len < n)
  277. sysfatal("short record %d < %d", b->len, n);
  278. if(Bread(b->bp, p, n) != n)
  279. sysfatal("unexpected EOF - %r");
  280. b->len -= n;
  281. }
  282. void
  283. xd(Biff *b)
  284. {
  285. uvlong off;
  286. uchar buf[16];
  287. int addr, got, n, i, j;
  288. addr = 0;
  289. off = Boffset(b->bp);
  290. while(addr < b->len){
  291. n = (b->len >= sizeof(buf))? sizeof(buf): b->len;
  292. got = Bread(b->bp, buf, n);
  293. Bprint(bo, " %6d ", addr);
  294. addr += n;
  295. for(i = 0; i < got; i++)
  296. Bprint(bo, "%02x ", buf[i]);
  297. for(j = i; j < 16; j++)
  298. Bprint(bo, " ");
  299. Bprint(bo, " ");
  300. for(i = 0; i < got; i++)
  301. Bprint(bo, "%c", isprint(buf[i])? buf[i]: '.');
  302. Bprint(bo, "\n");
  303. }
  304. Bseek(b->bp, off, 0);
  305. }
  306. static int
  307. getrec(Biff *b)
  308. {
  309. int c;
  310. if((c = Bgetc(b->bp)) == -1)
  311. return -1; // real EOF
  312. b->op = c;
  313. if((c = Bgetc(b->bp)) == -1)
  314. sysfatal("unexpected EOF - %r");
  315. b->op |= c << 8;
  316. if((c = Bgetc(b->bp)) == -1)
  317. sysfatal("unexpected EOF - %r");
  318. b->len = c;
  319. if((c = Bgetc(b->bp)) == -1)
  320. sysfatal("unexpected EOF - %r");
  321. b->len |= c << 8;
  322. if(b->op == 0 && b->len == 0)
  323. return -1;
  324. if(Debug){
  325. Bprint(bo, "op=0x%x len=%d\n", b->op, b->len);
  326. xd(b);
  327. }
  328. return 0;
  329. }
  330. static uvlong
  331. gint(Biff *b, int n)
  332. {
  333. int i, c;
  334. uvlong vl, rc;
  335. if(b->len < n)
  336. return -1;
  337. rc = 0;
  338. for(i = 0; i < n; i++){
  339. if((c = Bgetc(b->bp)) == -1)
  340. sysfatal("unexpected EOF - %r");
  341. b->len--;
  342. vl = c;
  343. rc |= vl << (8*i);
  344. }
  345. return rc;
  346. }
  347. double
  348. grk(Biff *b)
  349. {
  350. int f;
  351. uvlong n;
  352. double d;
  353. n = gint(b, 4);
  354. f = n & 3;
  355. n &= ~3LL;
  356. if(f & 2){
  357. d = n / 4.0;
  358. }
  359. else{
  360. n <<= 32;
  361. memcpy(&d, &n, sizeof(d));
  362. }
  363. if(f & 1)
  364. d /= 100.0;
  365. return d;
  366. }
  367. double
  368. gdoub(Biff *b)
  369. {
  370. double d;
  371. uvlong n = gint(b, 8);
  372. memcpy(&d, &n, sizeof(n));
  373. return d;
  374. }
  375. char *
  376. gstr(Biff *b, int len_width)
  377. {
  378. Rune r;
  379. char *buf, *p;
  380. int nch, w, ap, ln, rt, opt;
  381. enum {
  382. Unicode = 1,
  383. Asian_phonetic = 4,
  384. Rich_text = 8,
  385. };
  386. if(b->len < len_width){
  387. if(getrec(b) == -1)
  388. sysfatal("starting STRING expected CONTINUE, got EOF");
  389. if(b->op != 0x03c)
  390. sysfatal("starting STRING expected CONTINUE, got op=0x%x", b->op);
  391. }
  392. ln = gint(b, len_width);
  393. if(Biffver != Ver8){
  394. if((buf = calloc(ln+1, sizeof(char))) == nil)
  395. sysfatal("no memory");
  396. gmem(b, buf, ln);
  397. return buf;
  398. }
  399. if((buf = calloc(ln+1, sizeof(char)*UTFmax)) == nil)
  400. sysfatal("no memory");
  401. p = buf;
  402. if(ln == 0)
  403. return buf;
  404. nch = 0;
  405. *buf = 0;
  406. opt = gint(b, 1);
  407. if(opt & Rich_text)
  408. rt = gint(b, 2);
  409. else
  410. rt = 0;
  411. if(opt & Asian_phonetic)
  412. ap = gint(b, 4);
  413. else
  414. ap = 0;
  415. for(;;){
  416. w = (opt & Unicode)? sizeof(Rune): sizeof(char);
  417. while(b->len > 0){
  418. r = gint(b, w);
  419. p += runetochar(p, &r);
  420. if(++nch >= ln){
  421. if(rt)
  422. skip(b, rt*4);
  423. if(ap)
  424. skip(b, ap);
  425. return buf;
  426. }
  427. }
  428. if(getrec(b) == -1)
  429. sysfatal("in STRING expected CONTINUE, got EOF");
  430. if(b->op != 0x03c)
  431. sysfatal("in STRING expected CONTINUE, got op=0x%x", b->op);
  432. opt = gint(b, 1);
  433. }
  434. }
  435. void
  436. sst(Biff *b)
  437. {
  438. int n;
  439. skip(b, 4); // total # strings
  440. Nstrtab = gint(b, 4); // # unique strings
  441. if((Strtab = calloc(Nstrtab, sizeof(char *))) == nil)
  442. sysfatal("no memory");
  443. for(n = 0; n < Nstrtab; n++)
  444. Strtab[n] = gstr(b, 2);
  445. }
  446. void
  447. boolerr(Biff *b)
  448. {
  449. int r = gint(b, 2); // row
  450. int c = gint(b, 2); // col
  451. int f = gint(b, 2); // formatting ref
  452. int v = gint(b, 1); // bool value / err code
  453. int t = gint(b, 1); // type
  454. cell(r, c, f, (t)? Terror: Tbool, &v);
  455. }
  456. void
  457. rk(Biff *b)
  458. {
  459. int r = gint(b, 2); // row
  460. int c = gint(b, 2); // col
  461. int f = gint(b, 2); // formatting ref
  462. double v = grk(b); // value
  463. cell(r, c, f, Tnumber, &v);
  464. }
  465. void
  466. mulrk(Biff *b)
  467. {
  468. int r = gint(b, 2); // row
  469. int c = gint(b, 2); // first col
  470. while(b->len >= 6){
  471. int f = gint(b, 2); // formatting ref
  472. double v = grk(b); // value
  473. cell(r, c++, f, Tnumber, &v);
  474. }
  475. }
  476. void
  477. number(Biff *b)
  478. {
  479. int r = gint(b, 2); // row
  480. int c = gint(b, 2); // col
  481. int f = gint(b, 2); // formatting ref
  482. double v = gdoub(b); // double
  483. cell(r, c, f, Tnumber, &v);
  484. }
  485. void
  486. label(Biff *b)
  487. {
  488. int r = gint(b, 2); // row
  489. int c = gint(b, 2); // col
  490. int f = gint(b, 2); // formatting ref
  491. char *s = gstr(b, 2); // byte string
  492. cell(r, c, f, Tlabel, s);
  493. }
  494. void
  495. labelsst(Biff *b)
  496. {
  497. int r = gint(b, 2); // row
  498. int c = gint(b, 2); // col
  499. int f = gint(b, 2); // formatting ref
  500. int i = gint(b, 2); // sst string ref
  501. cell(r, c, f, Tindex, &i);
  502. }
  503. void
  504. bof(Biff *b)
  505. {
  506. Biffver = gint(b, 2);
  507. Content = gint(b, 2);
  508. }
  509. void
  510. defcolwidth(Biff *b)
  511. {
  512. Defwidth = gint(b, 2);
  513. }
  514. void
  515. datemode(Biff *b)
  516. {
  517. Datemode = gint(b, 2);
  518. }
  519. int
  520. wanted(char *range, int sheet)
  521. {
  522. int i, j;
  523. char *p;
  524. if (! range)
  525. return 1;
  526. p = range;
  527. while(*p){
  528. i = strtol(p, &p, 10);
  529. switch(*p){
  530. case '\0':
  531. case ',':
  532. if (i == sheet)
  533. return 1;
  534. break;
  535. case '-':
  536. j = strtol(p+1, &p, 10);
  537. if(sheet >= i && sheet <= j)
  538. return 1;
  539. break;
  540. default:
  541. sysfatal(" %s malformed range spec", range);
  542. break;
  543. }
  544. if (*p == ',')
  545. p++;
  546. }
  547. return 0;
  548. }
  549. void
  550. eof(Biff *b)
  551. {
  552. int i;
  553. struct {
  554. int n;
  555. char *s;
  556. } names[] = {
  557. 0x005, "Workbook globals",
  558. 0x006, "Visual Basic module",
  559. 0x010, "Worksheet",
  560. 0x020, "Chart",
  561. 0x040, "Macro sheet",
  562. 0x100, "Workspace file",
  563. };
  564. static int sheet = 0;
  565. if(! wanted(Sheetrange, ++sheet)){
  566. release();
  567. return;
  568. }
  569. if(Ncols != -1){
  570. if(All){
  571. for(i = 0; i < nelem(names); i++)
  572. if(names[i].n == Content){
  573. Bprint(bo, "\n# contents %s\n", names[i].s);
  574. dump();
  575. }
  576. }
  577. else
  578. if(Content == 0x10) // Worksheet
  579. dump();
  580. }
  581. release();
  582. USED(b);
  583. }
  584. void
  585. colinfo(Biff *b)
  586. {
  587. int c;
  588. int c1 = gint(b, 2);
  589. int c2 = gint(b, 2);
  590. int w = gint(b, 2);
  591. if(c1 < 0)
  592. sysfatal("negative column number (%d)", c1);
  593. if(c2 >= Nwidths)
  594. sysfatal("too many columns (%d > %d)", c2, Nwidths);
  595. w /= 256;
  596. if(w > 100)
  597. w = 100;
  598. if(w < 0)
  599. w = 0;
  600. for(c = c1; c <= c2; c++)
  601. Width[c] = w;
  602. }
  603. void
  604. xf(Biff *b)
  605. {
  606. int fmt;
  607. static int nalloc = 0;
  608. skip(b, 2);
  609. fmt = gint(b, 2);
  610. if(nalloc >= Nxf){
  611. nalloc += 20;
  612. if((Xf = realloc(Xf, nalloc*sizeof(int))) == nil)
  613. sysfatal("no memory");
  614. }
  615. Xf[Nxf++] = fmt;
  616. }
  617. void
  618. writeaccess(Biff *b)
  619. {
  620. Bprint(bo, "# author %s\n", gstr(b, 2));
  621. }
  622. void
  623. codepage(Biff *b)
  624. {
  625. int codepage = gint(b, 2);
  626. if(codepage != 1200) // 1200 == UTF-16
  627. Bprint(bo, "# codepage %d\n", codepage);
  628. }
  629. void
  630. xls2csv(Biobuf *bp)
  631. {
  632. int i;
  633. Biff biff, *b;
  634. struct {
  635. int op;
  636. void (*func)(Biff *);
  637. } dispatch[] = {
  638. 0x000a, eof,
  639. 0x0022, datemode,
  640. 0x0042, codepage,
  641. 0x0055, defcolwidth,
  642. 0x005c, writeaccess,
  643. 0x007d, colinfo,
  644. 0x00bd, mulrk,
  645. 0x00fc, sst,
  646. 0x00fd, labelsst,
  647. 0x0203, number,
  648. 0x0204, label,
  649. 0x0205, boolerr,
  650. 0x027e, rk,
  651. 0x0809, bof,
  652. 0x00e0, xf,
  653. };
  654. b = &biff;
  655. b->bp = bp;
  656. while(getrec(b) != -1){
  657. for(i = 0; i < nelem(dispatch); i++)
  658. if(b->op == dispatch[i].op)
  659. (*dispatch[i].func)(b);
  660. skip(b, b->len);
  661. }
  662. }
  663. void
  664. usage(void)
  665. {
  666. fprint(2, "usage: %s [-Dant] [-w worksheets] [-d delim] /mnt/doc/Workbook\n", argv0);
  667. exits("usage");
  668. }
  669. void
  670. main(int argc, char *argv[])
  671. {
  672. int i;
  673. Biobuf bin, bout, *bp;
  674. ARGBEGIN{
  675. case 'D':
  676. Debug = 1;
  677. break;
  678. case 'a':
  679. All = 1;
  680. break;
  681. case 'd':
  682. Delim = EARGF(usage());
  683. break;
  684. case 'n':
  685. Nopad = 1;
  686. break;
  687. case 't':
  688. Trunc = 1;
  689. break;
  690. case 'w':
  691. Sheetrange = EARGF(usage());
  692. break;
  693. default:
  694. usage();
  695. break;
  696. }ARGEND;
  697. if(argc != 1)
  698. usage();
  699. bo = &bout;
  700. quotefmtinstall();
  701. Binit(bo, OWRITE, 1);
  702. if(argc > 0) {
  703. for(i = 0; i < argc; i++){
  704. if((bp = Bopen(argv[i], OREAD)) == nil)
  705. sysfatal("%s cannot open - %r", argv[i]);
  706. xls2csv(bp);
  707. Bterm(bp);
  708. }
  709. } else {
  710. Binit(&bin, 0, OREAD);
  711. xls2csv(&bin);
  712. }
  713. exits(0);
  714. }