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