]>
Commit | Line | Data |
---|---|---|
ef416fc2 | 1 | /* |
c07d5b2d | 2 | * "$Id: mantohtml.c 177 2006-06-21 00:20:03Z jlovell $" |
ef416fc2 | 3 | * |
4 | * Man page to HTML conversion program. | |
5 | * | |
bd7854cb | 6 | * Copyright 2004-2006 by Easy Software Products. |
ef416fc2 | 7 | * |
8 | * These coded instructions, statements, and computer programs are the | |
9 | * property of Easy Software Products and are protected by Federal | |
10 | * copyright law. Distribution and use rights are outlined in the file | |
11 | * "LICENSE.txt" which should have been included with this file. If this | |
12 | * file is missing or damaged please contact Easy Software Products | |
13 | * at: | |
14 | * | |
15 | * Attn: CUPS Licensing Information | |
16 | * Easy Software Products | |
17 | * 44141 Airport View Drive, Suite 204 | |
18 | * Hollywood, Maryland 20636 USA | |
19 | * | |
20 | * Voice: (301) 373-9600 | |
21 | * EMail: cups-info@cups.org | |
22 | * WWW: http://www.cups.org | |
23 | * | |
24 | * Contents: | |
25 | * | |
26 | * main() - Convert a man page to HTML. | |
27 | * putc_entity() - Put a single character, using entities as needed. | |
28 | * strmove() - Move characters within a string. | |
29 | */ | |
30 | ||
31 | /* | |
32 | * Include necessary headers. | |
33 | */ | |
34 | ||
35 | #include <cups/string.h> | |
36 | #include <stdlib.h> | |
ecdc0628 | 37 | #include <unistd.h> |
ef416fc2 | 38 | |
39 | ||
40 | /* | |
41 | * Local functions... | |
42 | */ | |
43 | ||
44 | static void putc_entity(int ch, FILE *fp); | |
45 | static void strmove(char *d, const char *s); | |
46 | ||
47 | ||
48 | /* | |
49 | * 'main()' - Convert a man page to HTML. | |
50 | */ | |
51 | ||
52 | int /* O - Exit status */ | |
53 | main(int argc, /* I - Number of command-line args */ | |
54 | char *argv[]) /* I - Command-line arguments */ | |
55 | { | |
56 | FILE *infile, /* Input file */ | |
57 | *outfile; /* Output file */ | |
58 | char line[1024], /* Line from file */ | |
59 | *lineptr, /* Pointer into line */ | |
bd7854cb | 60 | *endptr, /* Pointer to end of current */ |
61 | endchar, /* End character */ | |
62 | *paren, /* Pointer to parenthesis */ | |
ef416fc2 | 63 | name[1024]; /* Man page name */ |
64 | int section, /* Man page section */ | |
65 | pre, /* Preformatted */ | |
66 | font, /* Current font */ | |
67 | blist, /* In a bullet list? */ | |
68 | list, /* In a list? */ | |
69 | linenum; /* Current line number */ | |
70 | const char *post; /* Text to add after the current line */ | |
71 | static const char /* Start/end tags for fonts */ | |
72 | * const start_fonts[] = { "", "<b>", "<i>" }, | |
73 | * const end_fonts[] = { "", "</b>", "</i>" }; | |
74 | ||
75 | /* | |
76 | * Check arguments... | |
77 | */ | |
78 | ||
79 | if (argc > 3) | |
80 | { | |
81 | fputs("Usage: mantohtml [filename.man [filename.html]]\n", stderr); | |
82 | return (1); | |
83 | } | |
84 | ||
85 | /* | |
86 | * Open files as needed... | |
87 | */ | |
88 | ||
89 | if (argc > 1) | |
90 | { | |
91 | if ((infile = fopen(argv[1], "r")) == NULL) | |
92 | { | |
93 | perror(argv[1]); | |
94 | return (1); | |
95 | } | |
96 | } | |
97 | else | |
98 | infile = stdin; | |
99 | ||
100 | if (argc > 2) | |
101 | { | |
102 | if ((outfile = fopen(argv[2], "w")) == NULL) | |
103 | { | |
104 | perror(argv[2]); | |
105 | fclose(infile); | |
106 | return (1); | |
107 | } | |
108 | } | |
109 | else | |
110 | outfile = stdout; | |
111 | ||
112 | /* | |
113 | * Read from input and write the output... | |
114 | */ | |
115 | ||
116 | fputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" " | |
117 | "\"http://www.w3.org/TR/REC-html40/loose.dtd\">\n" | |
118 | "<html>\n" | |
119 | "<!-- SECTION: Man Pages -->\n" | |
120 | "<head>\n" | |
121 | "\t<style type='text/css'><!--\n" | |
122 | "\th1, h2, h3, p { font-family: sans-serif; text-align: justify; }\n" | |
123 | "\ttt, pre a:link, pre a:visited, tt a:link, tt a:visited { font-weight: bold; color: #7f0000; }\n" | |
124 | "\tpre { font-weight: bold; color: #7f0000; margin-left: 2em; }\n" | |
125 | "\th1.title, h2.title, h3.title { border-bottom: solid 2px #000000; }\n" | |
126 | "\t--></style>\n", outfile); | |
127 | ||
128 | blist = 0; | |
129 | font = 0; | |
130 | list = 0; | |
131 | linenum = 0; | |
132 | pre = 0; | |
133 | post = NULL; | |
134 | section = -1; | |
135 | ||
136 | while (fgets(line, sizeof(line), infile)) | |
137 | { | |
138 | linenum ++; | |
139 | ||
140 | if (line[0] == '.') | |
141 | { | |
142 | /* | |
143 | * Strip leading whitespace... | |
144 | */ | |
145 | ||
146 | while (line[1] == ' ' || line[1] == '\t') | |
147 | strmove(line + 1, line + 2); | |
148 | ||
149 | /* | |
150 | * Process man page commands... | |
151 | */ | |
152 | ||
153 | if (!strncmp(line, ".TH ", 4) && section < 0) | |
154 | { | |
155 | /* | |
156 | * Grab man page title... | |
157 | */ | |
158 | ||
159 | sscanf(line + 4, "%s%d", name, §ion); | |
160 | ||
161 | fprintf(outfile, | |
bd7854cb | 162 | "\t<title>%s(%d)</title>\n" |
ef416fc2 | 163 | "</head>\n" |
164 | "<body>\n" | |
ef416fc2 | 165 | "%s", |
bd7854cb | 166 | name, section, start_fonts[font]); |
ef416fc2 | 167 | } |
168 | else if (section < 0) | |
169 | continue; | |
170 | else if (!strncmp(line, ".SH ", 4) || !strncmp(line, ".Sh ", 4)) | |
171 | { | |
172 | /* | |
173 | * Grab heading... | |
174 | */ | |
175 | ||
176 | int first = 1; | |
177 | ||
178 | fputs(end_fonts[font], outfile); | |
179 | ||
180 | if (blist) | |
181 | { | |
182 | fputs("</li>\n</ul>\n", outfile); | |
183 | blist = 0; | |
184 | } | |
185 | ||
186 | if (list) | |
187 | { | |
188 | if (list == 1) | |
189 | fputs("</dt>\n", outfile); | |
190 | else if (list) | |
191 | fputs("</dd>\n", outfile); | |
192 | ||
193 | fputs("</dl>\n", outfile); | |
194 | list = 0; | |
195 | } | |
196 | ||
bd7854cb | 197 | line[strlen(line) - 1] = '\0'; /* Strip LF */ |
198 | ||
ef416fc2 | 199 | if (line[2] == 'H') |
bd7854cb | 200 | fputs("<h2><a name='", outfile); |
ef416fc2 | 201 | else |
bd7854cb | 202 | fputs("<h3><a name='", outfile); |
203 | ||
204 | for (lineptr = line + 4; *lineptr; lineptr ++) | |
205 | if (*lineptr == '\"') | |
206 | continue; | |
207 | else if (*lineptr == ' ') | |
208 | putc_entity('_', outfile); | |
209 | else | |
210 | putc_entity(*lineptr, outfile); | |
211 | ||
212 | fputs("'>", outfile); | |
ef416fc2 | 213 | |
214 | for (lineptr = line + 4; *lineptr; lineptr ++) | |
215 | if (*lineptr == '\"') | |
216 | continue; | |
217 | else if (*lineptr == ' ') | |
218 | { | |
219 | putc_entity(' ', outfile); | |
220 | ||
221 | first = 1; | |
222 | } | |
bd7854cb | 223 | else |
ef416fc2 | 224 | { |
225 | if (first) | |
bd7854cb | 226 | putc_entity(*lineptr, outfile); |
ef416fc2 | 227 | else |
228 | putc_entity(tolower(*lineptr), outfile); | |
229 | ||
230 | first = 0; | |
231 | } | |
232 | ||
233 | if (line[2] == 'H') | |
bd7854cb | 234 | fprintf(outfile, "</a></h2>\n%s", start_fonts[font]); |
ef416fc2 | 235 | else |
bd7854cb | 236 | fprintf(outfile, "</a></h3>\n%s", start_fonts[font]); |
ef416fc2 | 237 | } |
238 | else if (!strncmp(line, ".LP", 3) || !strncmp(line, ".PP", 3)) | |
239 | { | |
240 | /* | |
241 | * New paragraph... | |
242 | */ | |
243 | ||
244 | fputs(end_fonts[font], outfile); | |
245 | ||
246 | if (blist) | |
247 | { | |
248 | fputs("</li>\n</ul>\n", outfile); | |
249 | blist = 0; | |
250 | } | |
251 | ||
252 | if (list) | |
253 | { | |
254 | if (list == 1) | |
255 | fputs("</dt>\n", outfile); | |
256 | else if (list) | |
257 | fputs("</dd>\n", outfile); | |
258 | ||
259 | fputs("</dl>\n", outfile); | |
260 | list = 0; | |
261 | } | |
262 | ||
263 | fputs("<p>", outfile); | |
264 | font = 0; | |
265 | } | |
266 | else if (!strncmp(line, ".TP ", 4)) | |
267 | { | |
268 | /* | |
269 | * Grab list... | |
270 | */ | |
271 | ||
272 | fputs(end_fonts[font], outfile); | |
273 | ||
274 | if (blist) | |
275 | { | |
276 | fputs("</li>\n</ul>\n", outfile); | |
277 | blist = 0; | |
278 | } | |
279 | ||
280 | if (!list) | |
281 | fputs("<dl>\n", outfile); | |
282 | else if (list == 1) | |
283 | fputs("</dt>\n", outfile); | |
284 | else if (list) | |
285 | fputs("</dd>\n", outfile); | |
286 | ||
287 | fputs("<dt>", outfile); | |
288 | list = 1; | |
289 | font = 0; | |
290 | } | |
291 | else if (!strncmp(line, ".br", 3)) | |
292 | { | |
293 | /* | |
294 | * Grab line break... | |
295 | */ | |
296 | ||
297 | if (list == 1) | |
298 | { | |
299 | fputs("</dt>\n<dd>", outfile); | |
300 | list = 2; | |
301 | } | |
302 | else if (list) | |
303 | fputs("</dd>\n<dd>", outfile); | |
304 | else | |
305 | fputs("<br>\n", outfile); | |
306 | } | |
307 | else if (!strncmp(line, ".de ", 4)) | |
308 | { | |
309 | /* | |
310 | * Define macro - ignore... | |
311 | */ | |
312 | ||
313 | while (fgets(line, sizeof(line), infile)) | |
314 | { | |
315 | linenum ++; | |
316 | ||
317 | if (!strncmp(line, "..", 2)) | |
318 | break; | |
319 | } | |
320 | } | |
321 | else if (!strncmp(line, ".RS", 3)) | |
322 | { | |
323 | /* | |
324 | * Indent... | |
325 | */ | |
326 | ||
327 | fputs("<div style='margin-left: 3em;'>\n", outfile); | |
328 | } | |
329 | else if (!strncmp(line, ".RE", 3)) | |
330 | { | |
331 | /* | |
332 | * Unindent... | |
333 | */ | |
334 | ||
335 | fputs("</div>\n", outfile); | |
336 | } | |
337 | else if (!strncmp(line, ".ds ", 4) || !strncmp(line, ".rm ", 4) || | |
338 | !strncmp(line, ".tr ", 4) || !strncmp(line, ".hy ", 4) || | |
339 | !strncmp(line, ".IX ", 4) || !strncmp(line, ".PD", 3) || | |
340 | !strncmp(line, ".Sp", 3)) | |
341 | { | |
342 | /* | |
343 | * Ignore unused commands... | |
344 | */ | |
345 | } | |
346 | else if (!strncmp(line, ".Vb", 3) || !strncmp(line, ".nf", 3)) | |
347 | { | |
348 | /* | |
349 | * Start preformatted... | |
350 | */ | |
351 | ||
352 | pre = 1; | |
353 | fputs("<pre>\n", outfile); | |
354 | } | |
355 | else if (!strncmp(line, ".Ve", 3) || !strncmp(line, ".fi", 3)) | |
356 | { | |
357 | /* | |
358 | * End preformatted... | |
359 | */ | |
360 | ||
361 | if (pre) | |
362 | { | |
363 | pre = 0; | |
364 | fputs("</pre>\n", outfile); | |
365 | } | |
366 | } | |
367 | else if (!strncmp(line, ".IP \\(bu", 8)) | |
368 | { | |
369 | /* | |
370 | * Bullet list... | |
371 | */ | |
372 | ||
373 | if (blist) | |
374 | fputs("</li>\n", outfile); | |
375 | else | |
376 | { | |
377 | fputs("<ul>\n", outfile); | |
378 | blist = 1; | |
379 | } | |
380 | ||
381 | fputs("<li>", outfile); | |
382 | } | |
383 | else if (!strncmp(line, ".IP ", 4)) | |
384 | { | |
385 | /* | |
386 | * Indented paragraph... | |
387 | */ | |
388 | ||
389 | if (blist) | |
390 | { | |
391 | fputs("</li>\n</ul>\n", outfile); | |
392 | blist = 0; | |
393 | } | |
394 | ||
395 | fputs("<p style='margin-left: 3em;'>", outfile); | |
396 | ||
397 | for (lineptr = line + 4; isspace(*lineptr); lineptr ++); | |
398 | ||
399 | if (*lineptr == '\"') | |
400 | { | |
401 | strmove(line, lineptr + 1); | |
402 | ||
403 | if ((lineptr = strchr(line, '\"')) != NULL) | |
404 | *lineptr = '\0'; | |
405 | } | |
406 | else | |
407 | { | |
408 | strmove(line, lineptr); | |
409 | ||
410 | if ((lineptr = strchr(line, ' ')) != NULL) | |
411 | *lineptr = '\0'; | |
412 | } | |
413 | ||
414 | /* | |
415 | * Process the text as if it was in-line... | |
416 | */ | |
417 | ||
418 | post = "\n<br />\n<br />"; | |
419 | goto process_text; | |
420 | } | |
421 | else if (!strncmp(line, ".\\}", 3)) | |
422 | { | |
423 | /* | |
424 | * Ignore close block... | |
425 | */ | |
426 | } | |
427 | else if (!strncmp(line, ".ie", 3) || !strncmp(line, ".if", 3) || | |
428 | !strncmp(line, ".el", 3)) | |
429 | { | |
430 | /* | |
431 | * If/else - ignore... | |
432 | */ | |
433 | ||
434 | if (strchr(line, '{') != NULL) | |
435 | { | |
436 | /* | |
437 | * Skip whole block... | |
438 | */ | |
439 | ||
440 | while (fgets(line, sizeof(line), infile)) | |
441 | { | |
442 | linenum ++; | |
443 | ||
444 | if (strchr(line, '}') != NULL) | |
445 | break; | |
446 | } | |
447 | } | |
448 | } | |
449 | #if 0 | |
450 | else if (!strncmp(line, ". ", 4)) | |
451 | { | |
452 | /* | |
453 | * Grab ... | |
454 | */ | |
455 | } | |
456 | #endif /* 0 */ | |
457 | else if (!strncmp(line, ".B ", 3)) | |
458 | { | |
459 | /* | |
460 | * Grab bold text... | |
461 | */ | |
462 | ||
463 | fprintf(outfile, "%s<b>%s</b>%s", end_fonts[font], line + 3, | |
464 | start_fonts[font]); | |
465 | } | |
466 | else if (!strncmp(line, ".I ", 3)) | |
467 | { | |
468 | /* | |
469 | * Grab italic text... | |
470 | */ | |
471 | ||
472 | fprintf(outfile, "%s<i>%s</i>%s", end_fonts[font], line + 3, | |
473 | start_fonts[font]); | |
474 | } | |
475 | else if (strncmp(line, ".\\\"", 3)) | |
476 | { | |
477 | /* | |
478 | * Unknown... | |
479 | */ | |
480 | ||
481 | if ((lineptr = strchr(line, ' ')) != NULL) | |
482 | *lineptr = '\0'; | |
483 | else if ((lineptr = strchr(line, '\n')) != NULL) | |
484 | *lineptr = '\0'; | |
485 | ||
486 | fprintf(stderr, "mantohtml: Unknown man page command \'%s\' on line %d!\n", | |
487 | line, linenum); | |
488 | } | |
489 | ||
490 | /* | |
491 | * Skip continuation lines... | |
492 | */ | |
493 | ||
494 | lineptr = line + strlen(line) - 2; | |
495 | if (lineptr >= line && *lineptr == '\\') | |
496 | { | |
497 | while (fgets(line, sizeof(line), infile)) | |
498 | { | |
499 | linenum ++; | |
500 | lineptr = line + strlen(line) - 2; | |
501 | ||
502 | if (lineptr < line || *lineptr != '\\') | |
503 | break; | |
504 | } | |
505 | } | |
506 | } | |
507 | else | |
508 | { | |
509 | /* | |
510 | * Process man page text... | |
511 | */ | |
512 | ||
513 | process_text: | |
514 | ||
515 | for (lineptr = line; *lineptr; lineptr ++) | |
516 | { | |
bd7854cb | 517 | if (!strncmp(lineptr, "http://", 7)) |
518 | { | |
519 | /* | |
520 | * Embed URL... | |
521 | */ | |
522 | ||
523 | for (endptr = lineptr + 7; | |
524 | *endptr && !isspace(*endptr & 255); | |
525 | endptr ++); | |
526 | ||
527 | endchar = *endptr; | |
528 | *endptr = '\0'; | |
529 | ||
530 | fprintf(outfile, "<a href='%s'>%s</a>", lineptr, lineptr); | |
531 | *endptr = endchar; | |
532 | lineptr = endptr - 1; | |
533 | } | |
534 | else if (!strncmp(lineptr, "\\fI", 3) && | |
535 | (endptr = strstr(lineptr, "\\fR")) != NULL && | |
536 | (paren = strchr(lineptr, '(')) != NULL && | |
537 | paren < endptr) | |
538 | { | |
539 | /* | |
540 | * Link to man page? | |
541 | */ | |
542 | ||
543 | char manfile[1024], /* Man page filename */ | |
544 | manurl[1024]; /* Man page URL */ | |
545 | ||
546 | ||
547 | /* | |
548 | * See if the man file is available locally... | |
549 | */ | |
550 | ||
551 | lineptr += 3; | |
552 | endchar = *paren; | |
553 | *paren = '\0'; | |
554 | ||
555 | snprintf(manfile, sizeof(manfile), "%s.man", lineptr); | |
556 | snprintf(manurl, sizeof(manurl), "man-%s.html?TOPIC=Man+Pages", | |
557 | lineptr); | |
558 | ||
559 | *paren = endchar; | |
560 | endchar = *endptr; | |
561 | *endptr = '\0'; | |
562 | ||
563 | if (access(manfile, 0)) | |
564 | { | |
565 | /* | |
566 | * Not a local man page, just do it italic... | |
567 | */ | |
568 | ||
569 | fputs("<i>", outfile); | |
570 | while (*lineptr) | |
571 | putc_entity(*lineptr++, outfile); | |
572 | fputs("</i>", outfile); | |
573 | } | |
574 | else | |
575 | { | |
576 | /* | |
577 | * Local man page, do a link... | |
578 | */ | |
579 | ||
580 | fprintf(outfile, "<a href='%s'>", manurl); | |
581 | while (*lineptr) | |
582 | putc_entity(*lineptr++, outfile); | |
583 | fputs("</a>", outfile); | |
584 | } | |
585 | ||
586 | *endptr = endchar; | |
587 | lineptr = endptr + 2; | |
588 | } | |
589 | else if (*lineptr == '\\') | |
ef416fc2 | 590 | { |
591 | lineptr ++; | |
592 | if (!*lineptr) | |
593 | break; | |
594 | else if (isdigit(lineptr[0]) && isdigit(lineptr[1]) && | |
595 | isdigit(lineptr[2])) | |
596 | { | |
597 | fprintf(outfile, "&#%d;", ((lineptr[0] - '0') * 8 + | |
598 | lineptr[1] - '0') * 8 + | |
599 | lineptr[2] - '0'); | |
600 | lineptr += 2; | |
601 | } | |
602 | else if (*lineptr == '&') | |
603 | continue; | |
604 | else if (*lineptr == 's') | |
605 | { | |
606 | while (lineptr[1] == '-' || isdigit(lineptr[1])) | |
607 | lineptr ++; | |
608 | } | |
609 | else if (*lineptr == '*') | |
610 | { | |
611 | lineptr += 2; | |
612 | } | |
613 | else if (*lineptr != 'f') | |
614 | putc_entity(*lineptr, outfile); | |
615 | else | |
616 | { | |
617 | lineptr ++; | |
618 | if (!*lineptr) | |
619 | break; | |
620 | else | |
621 | { | |
622 | fputs(end_fonts[font], outfile); | |
623 | ||
624 | switch (*lineptr) | |
625 | { | |
626 | default : /* Regular */ | |
627 | font = 0; | |
628 | break; | |
629 | case 'B' : /* Bold */ | |
630 | case 'b' : | |
631 | font = 1; | |
632 | break; | |
633 | case 'I' : /* Italic */ | |
634 | case 'i' : | |
635 | font = 2; | |
636 | break; | |
637 | } | |
638 | ||
639 | fputs(start_fonts[font], outfile); | |
640 | } | |
641 | } | |
642 | } | |
643 | else | |
644 | putc_entity(*lineptr, outfile); | |
645 | } | |
646 | ||
647 | if (post) | |
648 | { | |
649 | fputs(post, outfile); | |
650 | post = NULL; | |
651 | } | |
652 | } | |
653 | } | |
654 | ||
655 | fprintf(outfile, "%s\n", end_fonts[font]); | |
656 | ||
657 | if (blist) | |
658 | { | |
659 | fputs("</li>\n</ul>\n", outfile); | |
660 | blist = 0; | |
661 | } | |
662 | ||
663 | if (list) | |
664 | { | |
665 | if (list == 1) | |
666 | fputs("</dt>\n", outfile); | |
667 | else if (list) | |
668 | fputs("</dd>\n", outfile); | |
669 | ||
670 | fputs("</dl>\n", outfile); | |
671 | list = 0; | |
672 | } | |
673 | ||
674 | fputs("</body>\n" | |
675 | "</html>\n", outfile); | |
676 | ||
677 | /* | |
678 | * Close files... | |
679 | */ | |
680 | ||
681 | if (infile != stdin) | |
682 | fclose(infile); | |
683 | ||
684 | if (outfile != stdout) | |
685 | fclose(outfile); | |
686 | ||
687 | /* | |
688 | * Return with no errors... | |
689 | */ | |
690 | ||
691 | return (0); | |
692 | } | |
693 | ||
694 | ||
695 | /* | |
696 | * 'putc_entity()' - Put a single character, using entities as needed. | |
697 | */ | |
698 | ||
699 | static void | |
700 | putc_entity(int ch, /* I - Character */ | |
701 | FILE *fp) /* I - File */ | |
702 | { | |
703 | if (ch == '&') | |
704 | fputs("&", fp); | |
705 | else if (ch == '<') | |
706 | fputs("<", fp); | |
707 | else | |
708 | putc(ch, fp); | |
709 | } | |
710 | ||
711 | ||
712 | /* | |
713 | * 'strmove()' - Move characters within a string. | |
714 | */ | |
715 | ||
716 | static void | |
717 | strmove(char *d, /* I - Destination */ | |
718 | const char *s) /* I - Source */ | |
719 | { | |
720 | while (*s) | |
721 | *d++ = *s++; | |
722 | ||
723 | *d = '\0'; | |
724 | } | |
725 | ||
726 | ||
727 | /* | |
c07d5b2d | 728 | * End of "$Id: mantohtml.c 177 2006-06-21 00:20:03Z jlovell $". |
ef416fc2 | 729 | */ |