• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Man page to HTML conversion program.
3  *
4  * Copyright 2025 by OpenPrinting.
5  * Copyright 2007-2017 by Apple Inc.
6  * Copyright 2004-2006 by Easy Software Products.
7  *
8  * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
9  */
10 
11 /*
12  * Include necessary headers.
13  */
14 
15 #include <cups/string-private.h>
16 #include <cups/array-private.h>
17 #include <unistd.h>
18 
19 
20 /*
21  * Local globals...
22  */
23 
24 static const char			/* Start/end tags for fonts */
25 	* const start_fonts[] = { "", "<b>", "<i>" },
26 	* const end_fonts[] = { "", "</b>", "</i>" };
27 
28 
29 /*
30  * Local functions...
31  */
32 
33 static void	html_alternate(const char *s, const char *first, const char *second, FILE *fp);
34 static void	html_fputs(const char *s, int *font, FILE *fp);
35 static void	html_putc(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 */
main(int argc,char * argv[])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 */
51 		anchor[1024],		/* Anchor */
52 		name[1024],		/* Man page name */
53 		ddpost[256];		/* Tagged list post markup */
54   int		section = -1,		/* Man page section */
55 		pre = 0,		/* Preformatted */
56 		font = 0,		/* Current font */
57 		linenum = 0;		/* Current line number */
58   float		list_indent = 0.0f,	/* Current list indentation */
59 		nested_indent = 0.0f;	/* Nested list indentation, if any */
60   const char	*list = NULL,		/* Current list, if any */
61 		*nested = NULL;		/* Nested list, if any */
62   const char 	*post = NULL;		/* Text to add after the current line */
63 
64 
65  /*
66   * Check arguments...
67   */
68 
69   if (argc > 3)
70   {
71     fputs("Usage: mantohtml [filename.man [filename.html]]\n", stderr);
72     return (1);
73   }
74 
75  /*
76   * Open files as needed...
77   */
78 
79   if (argc > 1)
80   {
81     if ((infile = fopen(argv[1], "r")) == NULL)
82     {
83       perror(argv[1]);
84       return (1);
85     }
86   }
87   else
88     infile = stdin;
89 
90   if (argc > 2)
91   {
92     if ((outfile = fopen(argv[2], "w")) == NULL)
93     {
94       perror(argv[2]);
95       fclose(infile);
96       return (1);
97     }
98   }
99   else
100     outfile = stdout;
101 
102  /*
103   * Read from input and write the output...
104   */
105 
106   fputs("<!DOCTYPE HTML>\n"
107         "<html>\n"
108 	"<!-- SECTION: Man Pages -->\n"
109 	"<head>\n"
110 	"\t<link rel=\"stylesheet\" type=\"text/css\" "
111 	"href=\"../cups-printable.css\">\n", outfile);
112 
113   anchor[0] = '\0';
114 
115   while (fgets(line, sizeof(line), infile))
116   {
117     size_t linelen = strlen(line);	/* Length of line */
118 
119     if (linelen > 0 && line[linelen - 1] == '\n')
120       line[linelen - 1] = '\0';
121 
122     linenum ++;
123 
124     if (line[0] == '.')
125     {
126      /*
127       * Strip leading whitespace...
128       */
129 
130       while (line[1] == ' ' || line[1] == '\t')
131         strmove(line + 1, line + 2);
132 
133      /*
134       * Process man page commands...
135       */
136 
137       if (!strncmp(line, ".TH ", 4) && section < 0)
138       {
139        /*
140         * Grab man page title...
141 	*/
142 
143         sscanf(line + 4, "%s%d", name, &section);
144 
145         fprintf(outfile,
146 	        "\t<title>%s(%d)</title>\n"
147 	        "</head>\n"
148 	        "<body>\n"
149 		"<h1 class=\"title\">%s(%d)</h1>\n"
150 		"%s",
151 	        name, section, name, section, start_fonts[font]);
152       }
153       else if (section < 0)
154         continue;
155       else if (!strncmp(line, ".SH ", 4) || !strncmp(line, ".SS ", 4))
156       {
157        /*
158         * Grab heading...
159 	*/
160 
161         int first = 1;
162 
163 	fputs(end_fonts[font], outfile);
164 	font = 0;
165 
166         if (list)
167 	{
168 	  fprintf(outfile, "</%s>\n", list);
169 	  list = NULL;
170 	}
171 
172         if (line[2] == 'H')
173 	  fputs("<h2 class=\"title\"><a name=\"", outfile);
174 	else
175 	  fputs("<h3><a name=\"", outfile);
176 
177         if (anchor[0])
178         {
179           fputs(anchor, outfile);
180           anchor[0] = '\0';
181         }
182         else
183         {
184 	  for (lineptr = line + 4; *lineptr; lineptr ++)
185 	    if (*lineptr  == '\"')
186 	      continue;
187 	    else if (isalnum(*lineptr & 255))
188 	      html_putc(*lineptr, outfile);
189 	    else
190 	      html_putc('_', outfile);
191         }
192 
193 	fputs("\">", outfile);
194 
195         for (lineptr = line + 4; *lineptr; lineptr ++)
196         {
197 	  if (*lineptr == '\"')
198 	    continue;
199 	  else if (*lineptr == ' ')
200 	  {
201 	    html_putc(' ', outfile);
202 
203             first = 1;
204 	  }
205 	  else
206 	  {
207 	    if (first)
208 	      html_putc(*lineptr, outfile);
209 	    else
210 	      html_putc(tolower(*lineptr & 255), outfile);
211 
212             first = 0;
213           }
214         }
215 
216         if (line[2] == 'H')
217 	  fputs("</a></h2>\n", outfile);
218 	else
219 	  fputs("</a></h3>\n", outfile);
220       }
221       else if (!strncmp(line, ".B ", 3))
222       {
223        /*
224         * Grab bold text...
225 	*/
226 
227 	fputs(end_fonts[font], outfile);
228 	font = 0;
229 
230         if (anchor[0])
231           fprintf(outfile, "<a name=\"%s\">", anchor);
232 
233         html_alternate(line + 3, "b", "b", outfile);
234 
235         if (anchor[0])
236         {
237           fputs("</a>", outfile);
238           anchor[0] = '\0';
239         }
240 
241 	if (post)
242 	{
243 	  fputs(post, outfile);
244 	  post = NULL;
245 	}
246       }
247       else if (!strncmp(line, ".I ", 3))
248       {
249        /*
250         * Grab italic text...
251 	*/
252 
253 	fputs(end_fonts[font], outfile);
254 	font = 0;
255 
256         if (anchor[0])
257           fprintf(outfile, "<a name=\"%s\">", anchor);
258 
259         html_alternate(line + 3, "i", "i", outfile);
260 
261         if (anchor[0])
262         {
263           fputs("</a>", outfile);
264           anchor[0] = '\0';
265         }
266 
267 	if (post)
268 	{
269 	  fputs(post, outfile);
270 	  post = NULL;
271 	}
272       }
273       else if (!strncmp(line, ".BI ", 4))
274       {
275        /*
276         * Alternating bold and italic text...
277         */
278 
279 	fputs(end_fonts[font], outfile);
280 	font = 0;
281 
282         if (anchor[0])
283           fprintf(outfile, "<a name=\"%s\">", anchor);
284 
285         html_alternate(line + 4, "b", "i", outfile);
286 
287         if (anchor[0])
288         {
289           fputs("</a>", outfile);
290           anchor[0] = '\0';
291         }
292 
293 	if (post)
294 	{
295 	  fputs(post, outfile);
296 	  post = NULL;
297 	}
298       }
299       else if (!strncmp(line, ".BR ", 4))
300       {
301        /*
302         * Alternating bold and roman (plain) text...
303         */
304 
305 	fputs(end_fonts[font], outfile);
306 	font = 0;
307 
308         if (anchor[0])
309           fprintf(outfile, "<a name=\"%s\">", anchor);
310 
311         html_alternate(line + 4, "b", NULL, outfile);
312 
313         if (anchor[0])
314         {
315           fputs("</a>", outfile);
316           anchor[0] = '\0';
317         }
318 
319 	if (post)
320 	{
321 	  fputs(post, outfile);
322 	  post = NULL;
323 	}
324       }
325       else if (!strncmp(line, ".IB ", 4))
326       {
327        /*
328         * Alternating italic and bold text...
329         */
330 
331 	fputs(end_fonts[font], outfile);
332 	font = 0;
333 
334         if (anchor[0])
335           fprintf(outfile, "<a name=\"%s\">", anchor);
336 
337         html_alternate(line + 4, "i", "b", outfile);
338 
339         if (anchor[0])
340         {
341           fputs("</a>", outfile);
342           anchor[0] = '\0';
343         }
344 
345 	if (post)
346 	{
347 	  fputs(post, outfile);
348 	  post = NULL;
349 	}
350       }
351       else if (!strncmp(line, ".IR ", 4))
352       {
353        /*
354         * Alternating italic and roman (plain) text...
355         */
356 
357 	fputs(end_fonts[font], outfile);
358 	font = 0;
359 
360         if (anchor[0])
361           fprintf(outfile, "<a name=\"%s\">", anchor);
362 
363         html_alternate(line + 4, "i", NULL, outfile);
364 
365         if (anchor[0])
366         {
367           fputs("</a>", outfile);
368           anchor[0] = '\0';
369         }
370 
371 	if (post)
372 	{
373 	  fputs(post, outfile);
374 	  post = NULL;
375 	}
376       }
377       else if (!strncmp(line, ".RB ", 4))
378       {
379        /*
380         * Alternating roman (plain) and bold text...
381         */
382 
383 	fputs(end_fonts[font], outfile);
384 	font = 0;
385 
386         if (anchor[0])
387           fprintf(outfile, "<a name=\"%s\">", anchor);
388 
389         html_alternate(line + 4, NULL, "b", outfile);
390 
391         if (anchor[0])
392         {
393           fputs("</a>", outfile);
394           anchor[0] = '\0';
395         }
396 
397 	if (post)
398 	{
399 	  fputs(post, outfile);
400 	  post = NULL;
401 	}
402       }
403       else if (!strncmp(line, ".RI ", 4))
404       {
405        /*
406         * Alternating roman (plain) and italic text...
407         */
408 
409 	fputs(end_fonts[font], outfile);
410 	font = 0;
411 
412         if (anchor[0])
413           fprintf(outfile, "<a name=\"%s\">", anchor);
414 
415         html_alternate(line + 4, NULL, "i", outfile);
416 
417         if (anchor[0])
418         {
419           fputs("</a>", outfile);
420           anchor[0] = '\0';
421         }
422 
423 	if (post)
424 	{
425 	  fputs(post, outfile);
426 	  post = NULL;
427 	}
428       }
429       else if (!strncmp(line, ".SB ", 4))
430       {
431        /*
432         * Alternating small and bold text...
433         */
434 
435 	fputs(end_fonts[font], outfile);
436 	font = 0;
437 
438         if (anchor[0])
439           fprintf(outfile, "<a name=\"%s\">", anchor);
440 
441         html_alternate(line + 4, "small", "b", outfile);
442 
443         if (anchor[0])
444         {
445           fputs("</a>", outfile);
446           anchor[0] = '\0';
447         }
448 
449 	if (post)
450 	{
451 	  fputs(post, outfile);
452 	  post = NULL;
453 	}
454       }
455       else if (!strncmp(line, ".SM ", 4))
456       {
457        /*
458         * Small text...
459         */
460 
461 	fputs(end_fonts[font], outfile);
462 	font = 0;
463 
464         if (anchor[0])
465           fprintf(outfile, "<a name=\"%s\">", anchor);
466 
467         html_alternate(line + 4, "small", "small", outfile);
468 
469         if (anchor[0])
470         {
471           fputs("</a>", outfile);
472           anchor[0] = '\0';
473         }
474 
475 	if (post)
476 	{
477 	  fputs(post, outfile);
478 	  post = NULL;
479 	}
480       }
481       else if (!strcmp(line, ".LP") || !strcmp(line, ".PP") || !strcmp(line, ".P"))
482       {
483        /*
484         * New paragraph...
485 	*/
486 
487 	fputs(end_fonts[font], outfile);
488 	font = 0;
489 
490         if (list)
491         {
492           fprintf(outfile, "</%s>\n", list);
493           list = NULL;
494         }
495 
496 	fputs("<p>", outfile);
497 
498         if (anchor[0])
499         {
500           fprintf(outfile, "<a name=\"%s\"></a>", anchor);
501           anchor[0] = '\0';
502         }
503       }
504       else if (!strcmp(line, ".RS") || !strncmp(line, ".RS ", 4))
505       {
506        /*
507         * Indent...
508 	*/
509 
510 	float amount = 3.0f;		/* Indentation */
511 
512         if (line[3])
513           amount = (float)atof(line + 4);
514 
515 	fputs(end_fonts[font], outfile);
516 	font = 0;
517 
518         if (list)
519         {
520           nested        = list;
521           list          = NULL;
522           nested_indent = list_indent;
523           list_indent   = 0.0f;
524         }
525 
526         fprintf(outfile, "<div style=\"margin-left: %.1fem;\">\n", amount - nested_indent);
527       }
528       else if (!strcmp(line, ".RE"))
529       {
530        /*
531         * Unindent...
532 	*/
533 
534 	fputs(end_fonts[font], outfile);
535 	font = 0;
536 
537         fputs("</div>\n", outfile);
538 
539         if (nested)
540         {
541           list   = nested;
542           nested = NULL;
543 
544           list_indent   = nested_indent;
545           nested_indent = 0.0f;
546         }
547       }
548       else if (!strcmp(line, ".HP") || !strncmp(line, ".HP ", 4))
549       {
550        /*
551         * Hanging paragraph...
552         *
553         * .HP i
554 	*/
555 
556 	float amount = 3.0f;		/* Indentation */
557 
558         if (line[3])
559           amount = (float)atof(line + 4);
560 
561 	fputs(end_fonts[font], outfile);
562 	font = 0;
563 
564         if (list)
565         {
566           fprintf(outfile, "</%s>\n", list);
567           list = NULL;
568         }
569 
570         fprintf(outfile, "<p style=\"margin-left: %.1fem; text-indent: %.1fem\">", amount, -amount);
571 
572         if (anchor[0])
573         {
574           fprintf(outfile, "<a name=\"%s\"></a>", anchor);
575           anchor[0] = '\0';
576         }
577 
578         if (line[1] == 'T')
579           post = "<br>\n";
580       }
581       else if (!strcmp(line, ".TP") || !strncmp(line, ".TP ", 4))
582       {
583        /*
584         * Tagged list...
585         *
586         * .TP i
587 	*/
588 
589 	float amount = 3.0f;		/* Indentation */
590 
591         if (line[3])
592           amount = (float)atof(line + 4);
593 
594 	fputs(end_fonts[font], outfile);
595 	font = 0;
596 
597         if (list && strcmp(list, "dl"))
598         {
599           fprintf(outfile, "</%s>\n", list);
600           list = NULL;
601         }
602 
603         if (!list)
604         {
605           fputs("<dl class=\"man\">\n", outfile);
606           list        = "dl";
607           list_indent = amount;
608         }
609 
610         fputs("<dt>", outfile);
611         snprintf(ddpost, sizeof(ddpost), "<dd style=\"margin-left: %.1fem\">", amount);
612 	post = ddpost;
613 
614         if (anchor[0])
615         {
616           fprintf(outfile, "<a name=\"%s\"></a>", anchor);
617           anchor[0] = '\0';
618         }
619       }
620       else if (!strncmp(line, ".IP ", 4))
621       {
622        /*
623         * Indented paragraph...
624         *
625         * .IP x i
626 	*/
627 
628         float amount = 3.0f;		/* Indentation */
629         const char *newlist = NULL;	/* New list style */
630         const char *newtype = NULL;	/* New list numbering type */
631 
632 	fputs(end_fonts[font], outfile);
633 	font = 0;
634 
635         lineptr = line + 4;
636         while (isspace(*lineptr & 255))
637           lineptr ++;
638 
639         if (!strncmp(lineptr, "\\(bu", 4) || !strncmp(lineptr, "\\(em", 4))
640 	{
641 	 /*
642 	  * Bullet list...
643 	  */
644 
645           newlist = "ul";
646 	}
647 	else if (isdigit(*lineptr & 255))
648 	{
649 	 /*
650 	  * Numbered list...
651 	  */
652 
653           newlist = "ol";
654 	}
655 	else if (islower(*lineptr & 255))
656 	{
657 	 /*
658 	  * Lowercase alpha list...
659 	  */
660 
661           newlist = "ol";
662           newtype = "a";
663 	}
664 	else if (isupper(*lineptr & 255))
665 	{
666 	 /*
667 	  * Lowercase alpha list...
668 	  */
669 
670           newlist = "ol";
671           newtype = "A";
672 	}
673 
674         while (!isspace(*lineptr & 255))
675           lineptr ++;
676         while (isspace(*lineptr & 255))
677           lineptr ++;
678 
679         if (isdigit(*lineptr & 255))
680           amount = (float)atof(lineptr);
681 
682         if (newlist && list && strcmp(newlist, list))
683         {
684           fprintf(outfile, "</%s>\n", list);
685           list = NULL;
686         }
687 
688         if (newlist && !list)
689         {
690           if (newtype)
691             fprintf(outfile, "<%s type=\"%s\">\n", newlist, newtype);
692           else
693             fprintf(outfile, "<%s>\n", newlist);
694 
695           list = newlist;
696         }
697 
698         if (list)
699           fprintf(outfile, "<li style=\"margin-left: %.1fem;\">", amount);
700         else
701           fprintf(outfile, "<p style=\"margin-left: %.1fem;\">", amount);
702 
703         if (anchor[0])
704         {
705           fprintf(outfile, "<a name=\"%s\"></a>", anchor);
706           anchor[0] = '\0';
707         }
708       }
709       else if (!strncmp(line, ".br", 3))
710       {
711        /*
712         * Grab line break...
713 	*/
714 
715 	fputs("<br>\n", outfile);
716       }
717       else if (!strncmp(line, ".de ", 4))
718       {
719        /*
720         * Define macro - ignore...
721 	*/
722 
723         while (fgets(line, sizeof(line), infile))
724 	{
725 	  linenum ++;
726 
727 	  if (!strncmp(line, "..", 2))
728 	    break;
729 	}
730       }
731       else if (!strncmp(line, ".ds ", 4) || !strncmp(line, ".rm ", 4) ||
732                !strncmp(line, ".tr ", 4) || !strncmp(line, ".hy ", 4) ||
733                !strncmp(line, ".IX ", 4) || !strncmp(line, ".PD", 3) ||
734 	       !strncmp(line, ".Sp", 3))
735       {
736        /*
737         * Ignore unused commands...
738 	*/
739       }
740       else if (!strncmp(line, ".Vb", 3) || !strncmp(line, ".nf", 3) || !strncmp(line, ".EX", 3))
741       {
742        /*
743         * Start preformatted...
744 	*/
745 
746 	fputs(end_fonts[font], outfile);
747 	font = 0;
748 
749 //        if (list)
750 //	{
751 //	  fprintf(outfile, "</%s>\n", list);
752 //	  list = NULL;
753 //	}
754 
755         pre = 1;
756 	fputs("<pre class=\"man\">\n", outfile);
757       }
758       else if (!strncmp(line, ".Ve", 3) || !strncmp(line, ".fi", 3) || !strncmp(line, ".EE", 3))
759       {
760        /*
761         * End preformatted...
762 	*/
763 
764 	fputs(end_fonts[font], outfile);
765 	font = 0;
766 
767         if (pre)
768 	{
769           pre = 0;
770 	  fputs("</pre>\n", outfile);
771 	}
772       }
773       else if (!strncmp(line, ".\\}", 3))
774       {
775        /*
776         * Ignore close block...
777 	*/
778       }
779       else if (!strncmp(line, ".ie", 3) || !strncmp(line, ".if", 3) ||
780                !strncmp(line, ".el", 3))
781       {
782        /*
783         * If/else - ignore...
784 	*/
785 
786         if (strchr(line, '{') != NULL)
787 	{
788 	 /*
789 	  * Skip whole block...
790 	  */
791 
792           while (fgets(line, sizeof(line), infile))
793 	  {
794 	    linenum ++;
795 
796 	    if (strchr(line, '}') != NULL)
797 	      break;
798           }
799 	}
800       }
801 #if 0
802       else if (!strncmp(line, ". ", 4))
803       {
804        /*
805         * Grab ...
806 	*/
807       }
808 #endif /* 0 */
809       else if (!strncmp(line, ".\\\"#", 4))
810       {
811        /*
812         * Anchor for HTML output...
813         */
814 
815         strlcpy(anchor, line + 4, sizeof(anchor));
816       }
817       else if (strncmp(line, ".\\\"", 3))
818       {
819        /*
820         * Unknown...
821 	*/
822 
823         if ((lineptr = strchr(line, ' ')) != NULL)
824 	  *lineptr = '\0';
825 	else if ((lineptr = strchr(line, '\n')) != NULL)
826 	  *lineptr = '\0';
827 
828         fprintf(stderr, "mantohtml: Unknown man page command \'%s\' on line %d.\n",  line, linenum);
829       }
830 
831      /*
832       * Skip continuation lines...
833       */
834 
835       lineptr = line + strlen(line) - 1;
836       if (lineptr >= line && *lineptr == '\\')
837       {
838         while (fgets(line, sizeof(line), infile))
839 	{
840 	  linenum ++;
841 	  lineptr = line + strlen(line) - 2;
842 
843 	  if (lineptr < line || *lineptr != '\\')
844 	    break;
845 	}
846       }
847     }
848     else
849     {
850      /*
851       * Process man page text...
852       */
853 
854       if (pre == 1)
855       {
856         pre ++;
857         if (!line[0])
858           continue;			// Skip initial blank line
859       }
860 
861       html_fputs(line, &font, outfile);
862       putc('\n', outfile);
863 
864       if (post)
865       {
866         fputs(post, outfile);
867 	post = NULL;
868       }
869     }
870   }
871 
872   fprintf(outfile, "%s\n", end_fonts[font]);
873   font = 0;
874 
875   if (list)
876   {
877     fprintf(outfile, "</%s>\n", list);
878     list = NULL;
879   }
880 
881   fputs("</body>\n"
882         "</html>\n", outfile);
883 
884  /*
885   * Close files...
886   */
887 
888   if (infile != stdin)
889     fclose(infile);
890 
891   if (outfile != stdout)
892     fclose(outfile);
893 
894  /*
895   * Return with no errors...
896   */
897 
898   return (0);
899 }
900 
901 
902 /*
903  * 'html_alternate()' - Alternate words between two styles of text.
904  */
905 
906 static void
html_alternate(const char * s,const char * first,const char * second,FILE * fp)907 html_alternate(const char *s,		/* I - String */
908                const char *first,	/* I - First style or NULL */
909                const char *second,	/* I - Second style of NULL */
910                FILE       *fp)		/* I - File */
911 {
912   int		i = 0;			/* Which style */
913   int		quote = 0;		/* Saw quote? */
914   int		dolinks,		/* Do hyperlinks to other man pages? */
915 		link = 0;		/* Doing a link now? */
916 
917 
918  /*
919   * Skip leading whitespace...
920   */
921 
922   while (isspace(*s & 255))
923     s ++;
924 
925   dolinks = first && !strcmp(first, "b") && !second;
926 
927   while (*s)
928   {
929     if (!i && dolinks)
930     {
931      /*
932       * See if we need to make a link to a man page...
933       */
934 
935       const char *end;			/* End of current word */
936       const char *next;			/* Start of next word */
937 
938       for (end = s; *end && !isspace(*end & 255); end ++);
939       for (next = end; isspace(*next & 255); next ++);
940 
941       if (isalnum(*s & 255) && *next == '(')
942       {
943         // "name (section)" - see if the man file is available locally...
944 	char	name[1024],		// Name
945 		manfile[1024],		// Man page filename
946 		manurl[1024];		// Man page URL
947 
948         strlcpy(name, s, sizeof(name));
949         if ((size_t)(end - s) < sizeof(name))
950           name[end - s] = '\0';
951 
952 	snprintf(manurl, sizeof(manurl), "man-%s.html?TOPIC=Man+Pages", name);
953 	snprintf(manfile, sizeof(manfile), "%s.%d", name, atoi(next + 1));
954 	if (!access(manfile, 0))
955 	{
956 	  // Local man page, do a link...
957 	  fprintf(fp, "<a href=\"%s\">", manurl);
958 	  link = 1;
959 	}
960       }
961     }
962 
963     if (!i && first)
964       fprintf(fp, "<%s>", first);
965     else if (i && second)
966       fprintf(fp, "<%s>", second);
967 
968     while ((!isspace(*s & 255) || quote) && *s)
969     {
970       if (*s == '\"')
971         quote = !quote;
972 
973       if (*s == '\\' && s[1])
974       {
975         s ++;
976         html_putc(*s++, fp);
977       }
978       else
979         html_putc(*s++, fp);
980     }
981 
982     if (!i && first)
983       fprintf(fp, "</%s>", first);
984     else if (i && second)
985       fprintf(fp, "</%s>", second);
986 
987     if (i && link)
988     {
989       fputs("</a>", fp);
990       link = 0;
991     }
992 
993     i ^= 1;
994 
995    /*
996     * Skip trailing whitespace...
997     */
998 
999     while (isspace(*s & 255))
1000       s ++;
1001   }
1002 
1003   putc('\n', fp);
1004 }
1005 
1006 /*
1007  * 'html_fputs()' - Output a string, quoting as needed HTML entities.
1008  */
1009 
1010 static void
html_fputs(const char * s,int * font,FILE * fp)1011 html_fputs(const char *s,		/* I  - String */
1012            int        *font,		/* IO - Font */
1013            FILE       *fp)		/* I  - File */
1014 {
1015   while (*s)
1016   {
1017     if (*s == '\\')
1018     {
1019       s ++;
1020       if (!*s)
1021 	break;
1022 
1023       if (*s == 'f')
1024       {
1025         int	newfont;		/* New font */
1026 
1027         s ++;
1028         if (!*s)
1029           break;
1030 
1031         if (!font)
1032         {
1033           s ++;
1034           continue;
1035         }
1036 
1037         switch (*s++)
1038         {
1039           case 'R' :
1040           case 'P' :
1041               newfont = 0;
1042               break;
1043 
1044           case 'b' :
1045           case 'B' :
1046               newfont = 1;
1047               break;
1048 
1049           case 'i' :
1050           case 'I' :
1051               newfont = 2;
1052               break;
1053 
1054           default :
1055               fprintf(stderr, "mantohtml: Unknown font \"\\f%c\" ignored.\n", s[-1]);
1056               newfont = *font;
1057               break;
1058         }
1059 
1060         if (newfont != *font)
1061         {
1062 	  fputs(end_fonts[*font], fp);
1063 	  *font = newfont;
1064 	  fputs(start_fonts[*font], fp);
1065 	}
1066       }
1067       else if (*s == '*')
1068       {
1069        /*
1070         * Substitute macro...
1071         */
1072 
1073         s ++;
1074         if (!*s)
1075           break;
1076 
1077         switch (*s++)
1078         {
1079           case 'R' :
1080               fputs("&reg;", fp);
1081               break;
1082 
1083           case '(' :
1084 	      if (!strncmp(s, "lq", 2))
1085 		fputs("&ldquo;", fp);
1086 	      else if (!strncmp(s, "rq", 2))
1087 		fputs("&rdquo;", fp);
1088               else if (!strncmp(s, "Tm", 2))
1089                 fputs("<sup>TM</sup>", fp);
1090               else
1091                 fprintf(stderr, "mantohtml: Unknown macro \"\\*(%2s\" ignored.\n", s);
1092 
1093               if (*s)
1094                 s ++;
1095               if (*s)
1096                 s ++;
1097               break;
1098 
1099           default :
1100               fprintf(stderr, "mantohtml: Unknown macro \"\\*%c\" ignored.\n", s[-1]);
1101               break;
1102         }
1103       }
1104       else if (*s == '(')
1105       {
1106         if (!strncmp(s, "(em", 3))
1107         {
1108           fputs("&mdash;", fp);
1109           s += 3;
1110         }
1111         else if (!strncmp(s, "(en", 3))
1112         {
1113           fputs("&ndash;", fp);
1114           s += 3;
1115         }
1116         else
1117         {
1118           putc(*s, fp);
1119           s ++;
1120         }
1121       }
1122       else if (*s == '[')
1123       {
1124        /*
1125         * Substitute escaped character...
1126         */
1127 
1128         s ++;
1129 	if (!strncmp(s, "co]", 3))
1130 	  fputs("&copy;", fp);
1131 	else if (!strncmp(s, "de]", 3))
1132 	  fputs("&deg;", fp);
1133         else if (!strncmp(s, "rg]", 3))
1134 	  fputs("&reg;", fp);
1135 	else if (!strncmp(s, "tm]", 3))
1136 	  fputs("<sup>TM</sup>", fp);
1137 
1138 	if (*s)
1139 	  s ++;
1140 	if (*s)
1141 	  s ++;
1142 	if (*s)
1143 	  s ++;
1144       }
1145       else if (isdigit(s[0]) && isdigit(s[1]) &&
1146 	       isdigit(s[2]))
1147       {
1148 	fprintf(fp, "&#%d;", ((s[0] - '0') * 8 + s[1] - '0') * 8 + s[2] - '0');
1149 	s += 3;
1150       }
1151       else
1152       {
1153         if (*s != '\\' && *s != '\"' && *s != '\'' && *s != '-')
1154         {
1155           fprintf(stderr, "mantohtml: Unrecognized escape \"\\%c\" ignored.\n", *s);
1156           html_putc('\\', fp);
1157         }
1158 
1159         html_putc(*s++, fp);
1160       }
1161     }
1162     else if (!strncmp(s, "http://", 7) || !strncmp(s, "https://", 8) || !strncmp(s, "ftp://", 6))
1163     {
1164      /*
1165       * Embed URL...
1166       */
1167 
1168       char	temp[1024],		// Temporary string
1169 		*tempptr;		// Pointer into temporary string
1170 
1171       for (tempptr = temp; *s && !isspace(*s & 255) && tempptr < (temp + sizeof(temp) - 1); s ++)
1172       {
1173         if (strchr(",.)", *s) && strchr(",. \n\r\t", s[1]))
1174         {
1175           // End of URL
1176           break;
1177         }
1178         else if (*s == '\\' && s[1])
1179         {
1180           // Escaped character
1181           s ++;
1182           *tempptr++ = *s;
1183         }
1184         else
1185         {
1186           // Regular character...
1187           *tempptr++ = *s;
1188         }
1189       }
1190 
1191       *tempptr = '\0';
1192       fprintf(fp, "<a href=\"%s\">%s</a>", temp, temp);
1193     }
1194     else
1195       html_putc(*s++ & 255, fp);
1196   }
1197 }
1198 
1199 
1200 /*
1201  * 'html_putc()' - Put a single character, using entities as needed.
1202  */
1203 
1204 static void
html_putc(int ch,FILE * fp)1205 html_putc(int  ch,			/* I - Character */
1206 	  FILE *fp)			/* I - File */
1207 {
1208   if (ch == '&')
1209     fputs("&amp;", fp);
1210   else if (ch == '<')
1211     fputs("&lt;", fp);
1212   else
1213     putc(ch, fp);
1214 }
1215 
1216 
1217 /*
1218  * 'strmove()' - Move characters within a string.
1219  */
1220 
1221 static void
strmove(char * d,const char * s)1222 strmove(char       *d,			/* I - Destination */
1223         const char *s)			/* I - Source */
1224 {
1225   while (*s)
1226     *d++ = *s++;
1227 
1228   *d = '\0';
1229 }
1230