/[csv2latex]/csv2latex.c
ViewVC logotype

Contents of /csv2latex.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 58 - (show annotations)
Sun Jul 1 19:43:44 2018 UTC (16 months, 2 weeks ago) by ben
File MIME type: text/plain
File size: 19696 byte(s)
document function
1 /*
2 * csv2latex.c, copyright © 2002- Benoît Rouits <brouits@free.fr>
3 *
4 *********************************************************
5 * csv2latex translates a .csv file to a LaTex document. *
6 *********************************************************
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; version 2 only
11 * of the License.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 *
23 * see the COPYING file included in the csv2latex package or
24 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
25 *
26 */
27
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <libgen.h>
32 #include <getopt.h>
33 #include <unistd.h>
34 #include <ctype.h>
35 #include "version.h"
36
37 #define PTOMM(p) (0.3528 * (p)) /* pt to mm */
38 #define HEIGHT PTOMM(592) /* \textheight default in pt */
39 #define SKIPR 1.2 /* default \baselineskip ratio (to multiply with font) */
40
41 typedef struct {
42 char* tab; /* actual escapes */
43 int size; /* escape tab len */
44 } texcape;
45
46 typedef struct {
47 char block; /* CSV delimitor if any */
48 char sep; /* CSV separator */
49 unsigned int cols; /* CSV columns */
50 unsigned int chars; /* CSV max data length */
51 unsigned int rows; /* CSV total number of lines */
52 char pos; /* position in cell (align) */
53 unsigned int lines; /* rows per LaTeX tabular */
54 unsigned int guess; /* guess or not the CSV format */
55 unsigned int header; /* put LaTeX document header or not */
56 unsigned int red; /* table reduction level (from 1 to 4)*/
57 unsigned int longtable; /* use package longtable */
58 unsigned int escape; /* escape TeX control chars or not */
59 unsigned int repeat; /* repeat table headers for each LaTeX table section or not */
60 unsigned int vlines; /* insert vertical lines between columns or not */
61 unsigned int hlines; /* insert horizontal lines between rows or not */
62 unsigned int paper; /* output paper type */
63 unsigned int font; /* expected font size */
64 unsigned int land; /* landscape mode */
65 char* clrrow; /* row graylevel (from 0 to 1) */
66 texcape* tex; /* TeX escapes */
67 } config;
68
69 #define MAXUINT ((unsigned int)(-1))
70
71 void rtfm(char* prog) {
72 printf("%s translates a csv file to a LaTeX file\n", basename(prog));
73 printf("Example: %s january_stats.csv > january_stats.tex\n", basename(prog));
74 printf("Usage: %s [--nohead] (LaTeX) no document header: useful for inclusion\n", basename(prog));
75 printf(" [--longtable] (LaTeX) use package longtable: useful for long input\n");
76 printf(" [--noescape] (LaTeX) do not escape text: useful for mixed CSV/TeX input\n");
77 printf(" [--guess] (CSV) guess separator and block |\n"
78 " [--separator <(c)omma|(s)emicolon|(t)ab|s(p)ace|co(l)on>] (CSV's comma)\n"
79 " [--block <(q)uote|(d)ouble|(n)one>] (CSV) block delimiter (e.g: none)\n");
80 printf(" [--lines n] (LaTeX) rows per table: useful for long tabulars |\n");
81 printf(" [--font n] font size used (in pt)\n");
82 printf(" [--position <l|c|r>] (LaTeX) text align in cells\n");
83 printf(" [--colorrows graylevel] (LaTeX) alternate gray rows (e.g: 0.75)\n");
84 printf(" [--reduce level] (LaTeX) reduce table size (e.g: 1)\n");
85 printf(" [--landscape] (LaTeX) use landscape mode\n");
86 printf(" [--repeatheader] (LaTeX) repeat table header (for long tables)\n");
87 printf(" [--nohlines] (LaTeX) don't put hline between table rows\n");
88 printf(" [--novlines] (LaTeX) don't put vline between columns\n");
89 printf(" csv_file.csv\n");
90 printf("The \"longtable\" option needs the {longtable} LaTeX package\n");
91 printf("The \"colorrows\" option needs the {colortbl} LaTeX package\n");
92 printf("The \"reduce\" option needs the {relsize} LaTeX package\n");
93 return;
94 }
95
96 config* parseOptions (config* conf, int argc, char **argv) {
97 /* thx to <vfebvre@lautre.net> */
98 int opt;
99 int tmp;
100
101 #if defined USE_GETOPT
102 #else
103 int longopt_index = 0;
104 static struct option long_options[] = {
105 {"help", 0, NULL, 'h'},
106 {"guess", 0, NULL, 'g'},
107 {"block", 1, NULL, 'b'},
108 {"lines", 1, NULL, 'l'},
109 {"noescape", 0, NULL, 'x'},
110 {"nohead", 0, NULL, 'n'},
111 {"version", 0, NULL, 'v'},
112 {"position", 1, NULL, 'p'},
113 {"separator", 1, NULL, 's'},
114 {"colorrows", 1, NULL, 'c'},
115 {"reduce", 1, NULL, 'r'},
116 {"longtable", 0, NULL, 't'},
117 {"repeatheader", 0, NULL, 'e'},
118 {"novlines", 0, NULL, 'y'},
119 {"nohlines", 0, NULL, 'z'},
120 {"landscape", 0, NULL, 'a'},
121 {"font", 1, NULL, 'f'},
122 {NULL, 0, NULL, 0} /* marks end-of-list */
123 };
124 #endif
125 #if defined USE_GETOPT
126 while ((opt = getopt (argc, argv, "hvgnxteyza?b:l:p:s:c:r:f:")) != EOF) {
127 #else
128 while ((opt = getopt_long (argc, argv, "hvgnxteyza?b:l:p:s:c:r:f:", long_options, &longopt_index)) > 0) {
129 #endif
130 switch (opt) {
131 case '?':
132 case 'h':
133 rtfm (argv[0]);
134 exit (EXIT_SUCCESS);
135 break;
136 case 'g': /* guess the CSV */
137 conf->guess = 1;
138 break;
139 case 't': /* use package longtable */ /* thx to <Christof.Bodner@infineon.com> */
140 conf->longtable = 1;
141 break;
142 case 'b': /* csv block delimiter */
143 if (optarg[0] == 'q')
144 conf->block = '\'';
145 else if (optarg[0] == 'd')
146 conf->block = '"';
147 else if (optarg[0] == 'n')
148 conf->block = 0; /* no block delimiter */
149 break;
150 case 'l': /* number of lines per TeX tabulars */
151 if (isdigit(optarg[0])) {
152 conf->lines = atoi(optarg);
153 } else {
154 fprintf(stderr,
155 "option \"lines\" need a positive integer value\n");
156 exit(EXIT_FAILURE);
157 }
158 break;
159 case 'n':
160 conf->header = 0;
161 break;
162 case 'x':
163 conf->escape = 0;
164 break;
165 case 'v': /* version */
166 printf ("%s © 2002- Benoît Rouits <brouits@free.fr>\n"
167 "\tVersion %s (%s)\n", PACKAGE, VERSION, RELEASE_DATE);
168 exit (EXIT_SUCCESS);
169 break;
170 case 'p': /* LaTeX position in cell */
171 conf->pos = optarg[0]; /* position char in cell */
172 break;
173 case 's': /* csv block separator */
174 if (optarg[0] == 'c')
175 conf->sep = ',';
176 else if (optarg[0] == 's')
177 conf->sep = ';';
178 else if (optarg[0] == 't')
179 conf->sep = '\t';
180 else if (optarg[0] == 'p')
181 conf->sep = ' ';
182 else if (optarg[0] == 'l')
183 conf->sep = ':';
184 break;
185 case 'c': /* color rows (thx to <jcorso@cse.Buffalo.EDU>) */
186 if (isdigit(optarg[0])) {
187 conf->clrrow = (char*)malloc(strlen(optarg)+1);
188 strcpy(conf->clrrow, optarg);
189 } else {
190 fprintf(stderr,
191 "option \"colorrows\" needs a real value between 0 and 1\n");
192 exit(EXIT_FAILURE);
193 }
194 break;
195 case 'r': /* reduce table size (original idea thx to <boaz.gezer@gmail.com>) */
196 if (isdigit(optarg[0])) {
197 tmp = atoi(optarg);
198 conf->red = (tmp>4) ? 4 : (tmp<0) ? 0 : tmp; /* [1-4] */
199 } else {
200 fprintf(stderr,
201 "option \"reduce\" needs an integer value between 1 and 4\n");
202 exit(EXIT_FAILURE);
203 }
204 break;
205 case 'e': /*repeat table header for each table section*/
206 conf->repeat = 1;
207 break;
208 case 'y': /*don't draw vlines between columns*/
209 conf->vlines = 0;
210 break;
211 case 'z': /*don't draw hlines between rows*/
212 conf->hlines = 0;
213 break;
214 case 'a': /*landscape*/
215 conf->land = 1;
216 break;
217 case 'f': /*output font size*/
218 if (isdigit(optarg[0])) {
219 conf->font = atoi(optarg);
220 } else {
221 fprintf(stderr,
222 "option \"font\" need a positive integer value\n");
223 exit(EXIT_FAILURE);
224 }
225 break;
226 }
227 }
228 return conf;
229 }
230
231 int guessCSV(config* conf, FILE* in) {
232 /* guess the block delimiter and the csv separator */
233 int token;
234
235 token = getc(in); /* first char is block delimiter */
236 if (token == EOF) {
237 fprintf(stderr, "ERROR: empty file ?\n");
238 return -1;
239 } else if (ispunct(token) || token == ' ') {
240 /* found first block delimiter, act this way */
241 conf->block = token;
242 fprintf(stderr, "Guessed '%c' as Block Delimiter\n",
243 conf->block);
244 /* stream file while token is printable data */
245 while ((token = getc(in)) != conf->block &&
246 token != '\n' &&
247 token != EOF)
248 {/* getc has been done */}
249 if (token == conf->block) {
250 /* second delimiter : next is separator */
251 conf->sep = getc(in);
252 fprintf(stderr, "Guessed '%c' as Separator\n",
253 conf->sep);
254 return 0;
255 } else {
256 return -1; /* what else ? */
257 }
258 } else { /* no block delimiter, act this way */
259 conf->block = 0;
260 fprintf(stderr, "Guessed No Block Delimiter\n");
261 /* stream file while input is not a control char */
262 while (!ispunct((token = getc(in))) &&
263 token != '\n' &&
264 token != EOF)
265 {/* getc has been done */}
266 /* guess CSV separator */
267 if (ispunct(token) || token == '\t' || token == ' ') {
268 conf->sep = token;
269 fprintf(stderr, "Guessed %c as Separator\n", conf->sep);
270 return 0;
271 } else { /* did not found any separator */
272 fprintf(stderr, "ERROR: Did not guess any Separator!\n");
273 return -1;
274 }
275 }
276 return 0;
277 }
278
279 void getMaximums(config* conf, FILE* in) {
280 /* gets the number of cols and chars of a csv file assuming a separator */
281 int token = 0;
282 int nosep = 0;
283 unsigned int curcol = 0;
284 unsigned int curchar = 0;
285 unsigned int inblock = 0;
286 /* init */
287 conf->chars = 0;
288 conf->cols = 0;
289 conf->rows = 0;
290
291 while (token != EOF) {
292 token = getc(in);
293
294 /* EOF ? */
295 if (token == EOF) {
296 continue;
297 }
298
299 /* decide the maximums */
300 if (token == '\n') {
301 curcol++;
302 conf->cols = (conf->cols<curcol) ? curcol : conf->cols;
303 conf->chars = (conf->chars<curchar) ? curchar : conf->chars;
304 conf->rows++;
305 curcol = 0;
306 curchar = 0;
307 inblock = 0; /* reset block state */
308 continue;
309 }
310
311 /* check implicit, non-guessed, block */
312 if (token == '\"') {
313 if (nosep == 0)
314 nosep = 1;
315 else
316 nosep = 0;
317 }
318
319 /* enter/quit a block */
320 if (conf->block && token == conf->block) {
321 inblock = !inblock;
322 continue;
323 }
324
325 /* count cols in current line */
326 if (token == conf->sep && ((conf->block && !inblock) || !conf->block) && nosep == 0) {
327 curcol++;
328 continue;
329 }
330
331 /* count chars in current cell */
332 if (token != conf->block && ((conf->block && inblock) || !conf->block)) {
333 curchar++;
334 continue;
335 }
336 }
337 return;
338 }
339
340 unsigned int guessLinesPerPage(unsigned int font, double height) {
341 /**
342 * return: estimated number of lines per page, given page height in milimeter and font size in point
343 */
344 return height / (PTOMM(SKIPR * font));
345 }
346
347
348 void doTeXsub(config* conf, char newsep, FILE* in, FILE* out) {
349 /* substitutes CSV sep by LaTeX newsep and some TeX code */
350 int token = 0;
351 int max;
352 int numcols;
353 unsigned int lines;
354 int inblock = 0;
355 int csvrows;
356 int firstrow = 1;
357 int nosep = 0;
358 int token1 = 0;
359 int token2 = 0;
360 char headerrow[1000];
361 headerrow[0] = '\0';
362
363 max = numcols = conf->cols;
364 csvrows = conf->rows;
365
366 /* optimize a bit lines per tabular */
367 if (!conf->longtable && !conf->lines) {
368 conf->lines = guessLinesPerPage(conf->font ? conf->font : 10, conf->land ? (HEIGHT / 1.414) : HEIGHT);
369 fprintf(stderr, "guessed %d lines per page\n", conf->lines);
370 }
371
372 /* choose infinity when conf->lines is 0 */
373 lines = (conf->lines) ? conf->lines : MAXUINT;
374
375 while (token != EOF) {
376 token2 = token1; /* second last character, used for detection of quotation marks */
377 token1 = token; /* last character, used for detection of quotation marks */
378 token = getc(in);
379
380 /* EOF ? */
381 if (token == EOF) {
382 continue;
383 }
384
385 /* new line ? */
386 if (token == '\n') {
387 inblock = 0; /* close block if any */
388 /* fill empty cols if any */
389 while (numcols > 1) {
390 putc(newsep, out);
391 numcols--;
392 }
393 if (!(firstrow && (conf->longtable && conf->repeat))) {
394 fprintf(out, "\\\\\n"); /* TeX new line */
395 } else { /* first row and repeat and longtable */
396 fprintf(out, "%s\\\\\n", headerrow);
397 }
398 if (conf->hlines) {
399 fprintf(out, "\\hline\n"); /* TeX draw hline */
400 }
401 if (firstrow && (conf->longtable && conf->repeat)) {
402 fprintf(out, "%s", "\\endhead\n");
403 }
404 if (firstrow && conf->repeat) {
405 char tmp[12];
406 sprintf(tmp, (conf->hlines ? "\\\\\n\\hline" : "\\\\\n"));
407 strcat(headerrow, tmp);
408 }
409 firstrow = 0;
410 numcols = max; /* reset numcols */
411 lines--;
412 csvrows--;
413 /* put a colored row or not (alternate) */
414 if (conf->clrrow && (lines % 2)) {
415 fprintf(out, "\\colorrow ");
416 }
417 /* if the LaTeX tabular is full create a new one, except if no more row or this is a long table */
418 if (!lines && csvrows && !conf->longtable) {
419 fprintf(out, "\\end{tabular}\n");
420 fprintf(out, "\\newline\n");
421 fprintf(out, "\\begin{tabular}{");
422 if (conf->vlines) {
423 putc('|', out);
424 }
425 while (numcols--) {
426 putc(conf->pos, out);
427 if (conf->vlines) {
428 putc('|', out);
429 }
430 }
431 fprintf(out, "}\n");
432 if (conf->hlines) {
433 fprintf(out, "\\hline\n");
434 }
435 if (conf->repeat && !conf->longtable) {
436 fprintf(out, "%s", headerrow);
437 putc('\n', out);
438 }
439 numcols = max;
440 lines = (conf->lines) ? conf->lines : MAXUINT;
441 }
442 /* else end of CSV data */
443 continue;
444 }
445
446 /* if commas in cells */
447 /* thx to <florian@heinze.at> */
448 if (token == '\"') {
449 if (nosep == 0)
450 nosep = 1;
451 else
452 nosep = 0;
453 }
454
455 /* new column ? */
456 if ((token == conf->sep && ((conf->block && !inblock) || !conf->block)) && nosep == 0) {
457 if (!(firstrow && (conf->longtable && conf->repeat)))
458 putc(newsep, out);
459 numcols--;
460 if (firstrow && conf->repeat)
461 {
462 char tmp[2];
463 tmp[0] = newsep;
464 tmp[1] = '\0';
465 strcat(headerrow, tmp);
466 }
467 continue;
468 }
469
470 /* enter/quit a block ? */
471 if (conf->block && token == conf->block) {
472 inblock = !inblock;
473 continue;
474 }
475
476 /* data ? */
477 if ((token != conf->block && ((conf->block && inblock) || !conf->block))
478 && ((token == '\"' && token1 == '\"' && token2 == '\"') || token != '\"')) {
479 /* look for special TeX char to escape */
480 /* FIXME: put all that into a subroutine */
481 int i = 0;
482 if (conf->escape)
483 for (i = 0; i < conf->tex->size; i++) {
484 if (token == conf->tex->tab[i]) {
485 switch (token) {
486 case '\\':
487 fprintf(out, "\\textbackslash{}");
488 if (firstrow && conf->repeat)
489 {
490 char tmp[17];
491 sprintf(tmp, "\\textbackslash{}");
492 strcat(headerrow, tmp);
493 }
494 break;
495 default:
496 fprintf(out, "\\%c", token);
497 if (firstrow && conf->repeat)
498 {
499 char tmp[3];
500 tmp[0] = '\\';
501 tmp[1] = token;
502 tmp[2] = '\0';
503 strcat(headerrow, tmp);
504 }
505 break;
506 }
507 break; /* there was some escaping */
508 }
509 }
510 /* or print raw char */
511 if ((i >= conf->tex->size) || (!conf->escape)) {
512 if (!(firstrow && (conf->longtable && conf->repeat)))
513 putc(token, out); /* do not print the header twice */
514 if (firstrow && conf->repeat)
515 {
516 char tmp[2];
517 tmp[0] = token;
518 tmp[1] = '\0';
519 strcat(headerrow, tmp);
520 }
521 }
522 continue;
523 }
524 /* do nothing if unexpected char: just loop */
525 }
526 return;
527 }
528
529 void doTeXdoc(config* conf, FILE* in, FILE* out) {
530 /* prepares the LaTeX tabular layout */
531 int maxcols;
532 int numcols;
533 char* relsize[5] = {"0", "0.5", "1", "2", "4"}; /* LaTeX relsize good values */
534 char* tabcolsep[5] = {"0", "0.05", "0.1", "0.2", "0.4"}; /* LaTeX tabcolsep good values */
535
536 numcols = maxcols = conf->cols;
537 if (conf->header) {
538 if (conf->land) {
539 if (conf->font)
540 fprintf(out, "\\documentclass[%dpt,a4paper,landscape]{article}\n", conf->font);
541 else {
542 fprintf(out, "\\documentclass[a4paper,landscape]{article}\n");
543 conf->font = 10; /* pt */
544 }
545 } else {
546 if (conf->font)
547 fprintf(out, "\\documentclass[%dpt,a4paper]{article}\n", conf->font);
548 else {
549 fprintf(out, "\\documentclass[a4paper]{article}\n");
550 conf->font = 10; /* pt */
551 }
552 }
553 fprintf(out, "\\usepackage[T1]{fontenc}\n");
554 fprintf(out, "\\usepackage[utf8]{inputenc}\n");
555 if (conf->red) {
556 fprintf(out, "\\usepackage{relsize}\n");
557 }
558 if (conf->clrrow) {
559 fprintf(out, "\\usepackage{colortbl}\n");
560 }
561 if (conf->longtable) {
562 fprintf(out, "\\usepackage{longtable}\n");
563 }
564 fprintf(out, "\\begin{document}\n");
565 }
566 if (conf->clrrow) {
567 fprintf(out, "\\def\\colorrow{\\rowcolor[gray]{%s}}\n",
568 conf->clrrow);
569 }
570 if (conf->red) {
571 fprintf(out, "\\relsize{-%s}\n", relsize[conf->red]);
572 fprintf(out, "\\addtolength\\tabcolsep{-%sem}\n", tabcolsep[conf->red]);
573 }
574 if (conf->longtable)
575 {
576 fprintf(out, "\\begin{longtable}{");
577 if (conf->vlines)
578 putc('|', out);
579 }
580 else
581 {
582 fprintf(out, "\\begin{tabular}{");
583 if (conf->vlines)
584 putc('|', out);
585 }
586 while (numcols--)
587 {
588 fprintf(out, "%c", conf->pos); /* position in cell */
589 if (conf->vlines)
590 putc('|', out);
591 }
592 fprintf(out, "}\n");
593 if (conf->hlines)
594 fprintf(out, "\\hline\n");
595 doTeXsub(conf, '&', in, out); /* & is LaTeX separator */
596 if (conf->longtable) {
597 fprintf(out, "\\end{longtable}\n");
598 } else {
599 fprintf(out, "\\end{tabular}\n");
600 }
601 if (conf->red) {
602 fprintf(out, "\\addtolength\\tabcolsep{+%sem}\n", tabcolsep[conf->red]);
603 fprintf(out, "\\relsize{+%s}\n", relsize[conf->red]);
604 }
605 if (conf->header) {
606 fprintf(out, "\\end{document}\n");
607 }
608 return;
609 }
610
611 int main (int argc, char **argv) {
612 FILE* fp;
613 config* conf;
614
615 extern int optind, opterr, optopt;
616
617 conf = (config*)malloc(sizeof(config));
618 /* defaults (ensure init): */
619 conf->cols = 1; /* CSV: if getMaximums fails */
620 conf->rows = 0; /* CSV: must be 0 */
621 conf->chars = 0; /* CSV: must be 0 */
622 conf->pos = 'l'; /* usual; LaTeX */
623 conf->lines = 40; /* usual; LaTeX */
624 conf->guess = 0; /* usual */
625 conf->sep = ','; /* default; csv */
626 conf->block = 0; /* default; csv */
627 conf->header = 1; /* usual; LaTeX */
628 conf->escape = 1; /* usual; LaTeX */
629 conf->clrrow = NULL; /* default; LaTeX */
630 conf->red = 0; /* default; LaTeX */
631 conf->longtable = 0; /* default; without package longtable */
632 conf->repeat = 0; /* default; do not repeat the header row */
633 conf->vlines = 1; /* default; draw lines between columns */
634 conf->hlines = 1; /* default; draw lines between rows */
635 conf->font = 0; /* default to LaTeX defaults */
636 conf->land = 0; /* default to LaTeX defaults */
637
638 /* TeX charaters to escape */
639 conf->tex = (texcape*)malloc(sizeof(texcape));
640 conf->tex->tab = "\\_#$%^&{}~";
641 conf->tex->size = strlen(conf->tex->tab);
642
643
644 conf = parseOptions(conf, argc, argv);
645
646 if (optind == argc) {
647 /* copy stdin into tmp file */
648 int c;
649 fp = tmpfile();
650 while (EOF != (c = getc(stdin))) {
651 putc(c, fp);
652 }
653 rewind(fp);
654 } else {
655 fp = fopen(argv[optind], "r");
656 }
657
658 if (!fp) {
659 fprintf(stderr, "Can't open file %s\n", argv[optind]);
660 exit(EXIT_FAILURE);
661 }
662
663 if (conf->guess) {
664 if (guessCSV(conf, fp)) {
665 fprintf(stderr, "Please run again by using --delimiter (if any) and --separator\n");
666 fclose(fp);
667 exit(EXIT_FAILURE);
668 }
669 rewind(fp);
670 }
671
672 getMaximums(conf, fp);
673 rewind(fp);
674
675 doTeXdoc(conf, fp, stdout);
676
677 free(conf->tex);
678 if (conf->clrrow)
679 free(conf->clrrow);
680 free(conf);
681 fclose(fp);
682
683 exit(EXIT_SUCCESS);
684 }

  ViewVC Help
Powered by ViewVC 1.1.26