2 This file is part of the KDE libraries
4 Copyright (C) 2005 Nicolas GOUTTE <goutte@kde.org>
9 // Start of verbatim comment
12 ** This program was written by Richard Verhoeven (NL:5482ZX35)
13 ** at the Eindhoven University of Technology. Email: rcb5@win.tue.nl
15 ** Permission is granted to distribute, modify and use this program as long
16 ** as this comment is not removed or changed.
19 // End of verbatim comment
22 * man2html-linux-1.0/1.1
23 * This version modified for Redhat/Caldera linux - March 1996.
24 * Michael Hamilton <michael@actrix.gen.nz>.
27 * Added support for BSD mandoc pages - I didn't have any documentation
28 * on the mandoc macros, so I may have missed some.
29 * Michael Hamilton <michael@actrix.gen.nz>.
32 * Renamed to avoid confusion (V for Verhoeven, H for Hamilton).
35 * Now uses /etc/man.config
36 * Added support for compressed pages.
37 * Added "length-safe" string operations for client input parameters.
38 * More secure, -M secured, and client input string lengths checked.
43 ** If you want to use this program for your WWW server, adjust the line
44 ** which defines the CGIBASE or compile it with the -DCGIBASE='"..."' option.
46 ** You have to adjust the built-in manpath to your local system. Note that
47 ** every directory should start and end with the '/' and that the first
48 ** directory should be "/" to allow a full path as an argument.
50 ** The program first check if PATH_INFO contains some information.
51 ** If it does (t.i. man2html/some/thing is used), the program will look
52 ** for a manpage called PATH_INFO in the manpath.
54 ** Otherwise the manpath is searched for the specified command line argument,
55 ** where the following options can be used:
57 ** name name of manpage (csh, printf, xv, troff)
58 ** section the section (1 2 3 4 5 6 7 8 9 n l 1v ...)
59 ** -M path an extra directory to look for manpages (replaces "/")
61 ** If man2html finds multiple manpages that satisfy the options, an index
62 ** is displayed and the user can make a choice. If only one page is
63 ** found, that page will be displayed.
65 ** man2html will add links to the converted manpages. The function add_links
66 ** is used for that. At the moment it will add links as follows, where
67 ** indicates what should match to start with:
69 ** Recognition Item Link
70 ** ----------------------------------------------------------
71 ** name(*) Manpage ../man?/name.*
73 ** name@hostname Email address mailto:name@hostname
75 ** method://string URL method://string
77 ** www.host.name WWW server http://www.host.name
79 ** ftp.host.name FTP server ftp://ftp.host.name
81 ** <file.h> Include file file:/usr/include/file.h
84 ** Since man2html does not check if manpages, hosts or email addresses exist,
85 ** some links might not work. For manpages, some extra checks are performed
86 ** to make sure not every () pair creates a link. Also out of date pages
87 ** might point to incorrect places.
89 ** The program will not allow users to get system specific files, such as
90 ** /etc/passwd. It will check that "man" is part of the specified file and
91 ** that "/../" isn't. Even if someone manages to get such file, man2html will
92 ** handle it like a manpage and will usually not produce any output (or crash).
94 ** If you find any bugs when normal manpages are converted, please report
95 ** them to me (rcb5@win.tue.nl) after you have checked that man(1) can handle
96 ** the manpage correct.
98 ** Known bugs and missing features:
100 ** * Equations are not converted at all.
101 ** * Tables are converted but some features are not possible in html.
102 ** * The tabbing environment is converted by counting characters and adding
103 ** spaces. This might go wrong (outside <PRE>)
104 ** * Some manpages rely on the fact that troff/nroff is used to convert
105 ** them and use features which are not descripted in the man manpages.
106 ** (definitions, calculations, conditionals, requests). I can't guarantee
107 ** that all these features work on all manpages. (I didn't have the
108 ** time to look through all the available manpages.)
111 #include "man2html.h"
113 # include <config-runtime.h>
122 #include <QtCore/QByteArray>
123 #include <QtCore/QDateTime>
124 #include <QtCore/QMap>
125 #include <QtCore/QStack>
126 #include <QtCore/QString>
128 #ifdef SIMPLE_MAN2HTML
132 # include <sys/stat.h>
133 # define kDebug(x) cerr
134 # define kWarning(x) cerr << "WARNING "
135 # define BYTEARRAY(x) x.constData()
137 # include <QTextCodec>
139 # include <kdeversion.h>
140 # define BYTEARRAY(x) x
147 #define NULL_TERMINATED(n) ((n) + 1)
149 #define HUGE_STR_MAX 10000
150 #define LARGE_STR_MAX 2000
151 #define MED_STR_MAX 500
152 #define SMALL_STR_MAX 100
153 #define TINY_STR_MAX 10
157 // The output is current too horrible to be called HTML 4.01, so give no
161 #define DOCTYPE "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
164 /* mdoc(7) Bl/El lists to HTML list types */
165 #define BL_DESC_LIST 1
166 #define BL_BULLET_LIST 2
167 #define BL_ENUM_LIST 4
169 /* mdoc(7) Bd/Ed example(?) blocks */
173 static int s_nroff
= 1; // NROFF mode by default
175 static int mandoc_name_count
= 0; /* Don't break on the first Nm */
177 static char *stralloc(int len
)
179 /* allocate enough for len + NULL */
180 char *news
= new char [len
+1];
181 #ifdef SIMPLE_MAN2HTML
184 cerr
<< "man2html: out of memory" << endl
;
188 // modern compilers do not return a NULL pointer for a new
193 static char *strlimitcpy(char *to
, char *from
, int n
, int limit
)
194 { /* Assumes space for limit plus a null */
195 const int len
= n
> limit
? limit
: n
;
196 qstrncpy(to
, from
, len
+ 1);
201 /* below this you should not change anything unless you know a lot
202 ** about this program or about troff.
206 /// Structure for character definitions
214 const char NEWLINE
[2]="\n";
217 * Class for defining strings and macros
219 class StringDefinition
222 StringDefinition( void ) : m_length(0) {}
223 StringDefinition( int len
, const char* cstr
) : m_length( len
), m_output( cstr
) {}
225 int m_length
; ///< Length of output text
226 QByteArray m_output
; ///< Defined string
230 * Class for defining number registers
231 * \note Not for internal read-only registers
233 class NumberDefinition
236 NumberDefinition( void ) : m_value(0), m_increment(0) {}
237 NumberDefinition( int value
) : m_value( value
), m_increment(0) {}
238 NumberDefinition( int value
, int incr
) : m_value( value
), m_increment( incr
) {}
240 int m_value
; ///< value of number register
241 int m_increment
; ///< Increment of number register
242 // ### TODO: display form (.af)
246 * Map of character definitions
248 static QMap
<QByteArray
,StringDefinition
> s_characterDefinitionMap
;
251 * Map of string variable and macro definitions
252 * \note String variables and macros are the same thing!
254 static QMap
<QByteArray
,StringDefinition
> s_stringDefinitionMap
;
257 * Map of number registers
258 * \note Intern number registers (starting with a dot are not handled here)
260 static QMap
<QByteArray
,NumberDefinition
> s_numberDefinitionMap
;
262 static void fill_old_character_definitions( void );
265 * Initialize character variables
267 static void InitCharacterDefinitions( void )
269 fill_old_character_definitions();
270 // ### HACK: as we are converting to HTML too early, define characters with HTML references
271 s_characterDefinitionMap
.insert( "<-", StringDefinition( 1, "←" ) ); // <-
272 s_characterDefinitionMap
.insert( "->", StringDefinition( 1, "→" ) ); // ->
273 s_characterDefinitionMap
.insert( "<>", StringDefinition( 1, "↔" ) ); // <>
274 s_characterDefinitionMap
.insert( "<=", StringDefinition( 1, "≤" ) ); // <=
275 s_characterDefinitionMap
.insert( ">=", StringDefinition( 1, "≥" ) ); // >=
280 * Initialize string variables
282 static void InitStringDefinitions( void )
284 // mdoc-only, see mdoc.samples(7)
285 s_stringDefinitionMap
.insert( "<=", StringDefinition( 1, "≤" ) );
286 s_stringDefinitionMap
.insert( ">=", StringDefinition( 1, "≥" ) );
287 s_stringDefinitionMap
.insert( "Rq", StringDefinition( 1, "”" ) );
288 s_stringDefinitionMap
.insert( "Lq", StringDefinition( 1, "“" ) );
289 s_stringDefinitionMap
.insert( "ua", StringDefinition( 1, "&circ" ) ); // Note this is different from \(ua
290 s_stringDefinitionMap
.insert( "aa", StringDefinition( 1, "´" ) );
291 s_stringDefinitionMap
.insert( "ga", StringDefinition( 1, "`" ) );
292 s_stringDefinitionMap
.insert( "q", StringDefinition( 1, """ ) );
293 s_stringDefinitionMap
.insert( "Pi", StringDefinition( 1, "π" ) );
294 s_stringDefinitionMap
.insert( "Ne", StringDefinition( 1, "≠" ) );
295 s_stringDefinitionMap
.insert( "Le", StringDefinition( 1, "≤" ) );
296 s_stringDefinitionMap
.insert( "Ge", StringDefinition( 1, "≥" ) );
297 s_stringDefinitionMap
.insert( "Lt", StringDefinition( 1, "<" ) );
298 s_stringDefinitionMap
.insert( "Gt", StringDefinition( 1, ">" ) );
299 s_stringDefinitionMap
.insert( "Pm", StringDefinition( 1, "±" ) );
300 s_stringDefinitionMap
.insert( "If", StringDefinition( 1, "∞" ) );
301 s_stringDefinitionMap
.insert( "Na", StringDefinition( 3, "NaN" ) );
302 s_stringDefinitionMap
.insert( "Ba", StringDefinition( 1, "|" ) );
305 s_stringDefinitionMap
.insert( "Tm", StringDefinition( 1, "™" ) ); // \*(TM
306 s_stringDefinitionMap
.insert( "R", StringDefinition( 1, "®" ) ); // \*R
308 // Missing characters from man(7):
309 // \*S "Change to default font size"
310 #ifndef SIMPLE_MAN2HTML
311 // Special KDE KIO man:
312 const QByteArray
kdeversion(KDE_VERSION_STRING
);
313 s_stringDefinitionMap
.insert( ".KDE_VERSION_STRING", StringDefinition( kdeversion
.length(), kdeversion
) );
318 * Initialize number registers
319 * \note Internal read-only registers are not handled here
321 static void InitNumberDefinitions( void )
323 // As the date number registers are more for end-users, better choose local time.
324 // Groff seems to support Gregorian dates only
325 QDate
today( QDate::currentDate() );
326 s_numberDefinitionMap
.insert( "year", today
.year() ); // Y2K-correct year
327 s_numberDefinitionMap
.insert( "yr", today
.year() - 1900 ); // Y2K-incorrect year
328 s_numberDefinitionMap
.insert( "mo", today
.month() );
329 s_numberDefinitionMap
.insert( "dy", today
.day() );
330 s_numberDefinitionMap
.insert( "dw", today
.dayOfWeek() );
334 #define V(A,B) ((A)*256+(B))
336 //used in expand_char, e.g. for "\(bu"
337 // see groff_char(7) for list
338 static CSTRDEF standardchar
[] = {
339 { V('*','*'), 1, "*" },
340 { V('*','A'), 1, "Α" },
341 { V('*','B'), 1, "Β" },
342 { V('*','C'), 1, "Ξ" },
343 { V('*','D'), 1, "Δ" },
344 { V('*','E'), 1, "Ε" },
345 { V('*','F'), 1, "Φ" },
346 { V('*','G'), 1, "Γ" },
347 { V('*','H'), 1, "Θ" },
348 { V('*','I'), 1, "Ι" },
349 { V('*','K'), 1, "Κ" },
350 { V('*','L'), 1, "Λ" },
351 { V('*','M'), 1, "&Mu:" },
352 { V('*','N'), 1, "Ν" },
353 { V('*','O'), 1, "Ο" },
354 { V('*','P'), 1, "Π" },
355 { V('*','Q'), 1, "Ψ" },
356 { V('*','R'), 1, "Ρ" },
357 { V('*','S'), 1, "Σ" },
358 { V('*','T'), 1, "Τ" },
359 { V('*','U'), 1, "Υ" },
360 { V('*','W'), 1, "Ω" },
361 { V('*','X'), 1, "Χ" },
362 { V('*','Y'), 1, "Η" },
363 { V('*','Z'), 1, "Ζ" },
364 { V('*','a'), 1, "α"},
365 { V('*','b'), 1, "β"},
366 { V('*','c'), 1, "ξ"},
367 { V('*','d'), 1, "δ"},
368 { V('*','e'), 1, "ε"},
369 { V('*','f'), 1, "φ"},
370 { V('*','g'), 1, "γ"},
371 { V('*','h'), 1, "θ"},
372 { V('*','i'), 1, "ι"},
373 { V('*','k'), 1, "κ"},
374 { V('*','l'), 1, "λ"},
375 { V('*','m'), 1, "μ" },
376 { V('*','n'), 1, "ν"},
377 { V('*','o'), 1, "ο"},
378 { V('*','p'), 1, "π"},
379 { V('*','q'), 1, "ψ"},
380 { V('*','r'), 1, "ρ"},
381 { V('*','s'), 1, "σ"},
382 { V('*','t'), 1, "τ"},
383 { V('*','u'), 1, "υ"},
384 { V('*','w'), 1, "ω"},
385 { V('*','x'), 1, "χ"},
386 { V('*','y'), 1, "η"},
387 { V('*','z'), 1, "ζ"},
388 { V('+','-'), 1, "±" }, // not in groff_char(7)
389 { V('+','f'), 1, "φ"}, // phi1, we use the standard phi
390 { V('+','h'), 1, "θ"}, // theta1, we use the standard theta
391 { V('+','p'), 1, "ω"}, // omega1, we use the standard omega
392 { V('1','2'), 1, "½" },
393 { V('1','4'), 1, "¼" },
394 { V('3','4'), 1, "¾" },
395 { V('F','i'), 1, "ffi" }, // ffi ligature
396 { V('F','l'), 1, "ffl" }, // ffl ligature
397 { V('a','p'), 1, "~" },
398 { V('b','r'), 1, "|" },
399 { V('b','u'), 1, "•" },
400 { V('b','v'), 1, "|" },
401 { V('c','i'), 1, "○" }, // circle ### TODO verify
402 { V('c','o'), 1, "©" },
403 { V('c','t'), 1, "¢" },
404 { V('d','e'), 1, "°" },
405 { V('d','g'), 1, "†" },
406 { V('d','i'), 1, "÷" },
407 { V('e','m'), 1, "&emdash;" },
408 { V('e','n'), 1, "&endash;"},
409 { V('e','q'), 1, "=" },
410 { V('e','s'), 1, "∅" },
411 { V('f','f'), 1, "�xFB00;" }, // ff ligature
412 { V('f','i'), 1, "�xFB01;" }, // fi ligature
413 { V('f','l'), 1, "�xFB02;" }, // fl ligature
414 { V('f','m'), 1, "′" },
415 { V('g','a'), 1, "`" },
416 { V('h','y'), 1, "-" },
417 { V('l','c'), 2, "|¯" }, // ### TODO: not in groff_char(7)
418 { V('l','f'), 2, "|_" }, // ### TODO: not in groff_char(7)
419 { V('l','k'), 1, "<FONT SIZE=+2>{</FONT>" }, // ### TODO: not in groff_char(7)
420 { V('m','i'), 1, "-" }, // ### TODO: not in groff_char(7)
421 { V('m','u'), 1, "×" },
422 { V('n','o'), 1, "¬" },
423 { V('o','r'), 1, "|" },
424 { V('p','l'), 1, "+" },
425 { V('r','c'), 2, "¯|" }, // ### TODO: not in groff_char(7)
426 { V('r','f'), 2, "_|" }, // ### TODO: not in groff_char(7)
427 { V('r','g'), 1, "®" },
428 { V('r','k'), 1, "<FONT SIZE=+2>}</FONT>" }, // ### TODO: not in groff_char(7)
429 { V('r','n'), 1, "‾" },
430 { V('r','u'), 1, "_" },
431 { V('s','c'), 1, "§" },
432 { V('s','l'), 1, "/" },
433 { V('s','q'), 2, "□" }, // WHITE SQUARE
434 { V('t','s'), 1, "ς" }, // FINAL SIGMA
435 { V('u','l'), 1, "_" },
436 { V('-','D'), 1, "Ð" },
437 { V('S','d'), 1, "ð" },
438 { V('T','P'), 1, "Þ" },
439 { V('T','p'), 1, "þ" },
440 { V('A','E'), 1, "Æ" },
441 { V('a','e'), 1, "æ" },
442 { V('O','E'), 1, "Œ" },
443 { V('o','e'), 1, "œ" },
444 { V('s','s'), 1, "ß" },
445 { V('\'','A'), 1, "Á" },
446 { V('\'','E'), 1, "É" },
447 { V('\'','I'), 1, "Í" },
448 { V('\'','O'), 1, "Ó" },
449 { V('\'','U'), 1, "Ú" },
450 { V('\'','Y'), 1, "Ý" },
451 { V('\'','a'), 1, "á" },
452 { V('\'','e'), 1, "é" },
453 { V('\'','i'), 1, "í" },
454 { V('\'','o'), 1, "ó" },
455 { V('\'','u'), 1, "ú" },
456 { V('\'','y'), 1, "ý" },
457 { V(':','A'), 1, "Ä" },
458 { V(':','E'), 1, "Ë" },
459 { V(':','I'), 1, "Ï" },
460 { V(':','O'), 1, "Ö" },
461 { V(':','U'), 1, "Ü" },
462 { V(':','a'), 1, "ä" },
463 { V(':','e'), 1, "ë" },
464 { V(':','i'), 1, "ï" },
465 { V(':','o'), 1, "ö" },
466 { V(':','u'), 1, "ü" },
467 { V(':','y'), 1, "ÿ" },
468 { V('^','A'), 1, "Â" },
469 { V('^','E'), 1, "Ê" },
470 { V('^','I'), 1, "Î" },
471 { V('^','O'), 1, "Ô" },
472 { V('^','U'), 1, "Û" },
473 { V('^','a'), 1, "â" },
474 { V('^','e'), 1, "ê" },
475 { V('^','i'), 1, "î" },
476 { V('^','o'), 1, "ô" },
477 { V('^','u'), 1, "û" },
478 { V('`','A'), 1, "À" },
479 { V('`','E'), 1, "È" },
480 { V('`','I'), 1, "Ì" },
481 { V('`','O'), 1, "Ò" },
482 { V('`','U'), 1, "Ù" },
483 { V('`','a'), 1, "à" },
484 { V('`','e'), 1, "è" },
485 { V('`','i'), 1, "ì" },
486 { V('`','o'), 1, "ò" },
487 { V('`','u'), 1, "ù" },
488 { V('~','A'), 1, "Ã" },
489 { V('~','N'), 1, "Ñ" },
490 { V('~','O'), 1, "Õ" },
491 { V('~','a'), 1, "ã" },
492 { V('~','n'), 1, "&ntidle;" },
493 { V('~','o'), 1, "&otidle;" },
494 { V(',','C'), 1, "Ç" },
495 { V(',','c'), 1, "ç" },
496 { V('/','L'), 1, "Ł" },
497 { V('/','l'), 1, "ł" },
498 { V('/','O'), 1, "Ø" },
499 { V('/','o'), 1, "ø" },
500 { V('o','A'), 1, "Å" },
501 { V('o','a'), 1, "å" },
502 { V('a','"'), 1, "\"" },
503 { V('a','-'), 1, "¯" },
504 { V('a','.'), 1, "." },
505 { V('a','^'), 1, "ˆ" },
506 { V('a','a'), 1, "´" },
507 { V('a','b'), 1, "`" },
508 { V('a','c'), 1, "¸" },
509 { V('a','d'), 1, "¨" },
510 { V('a','h'), 1, "˂" }, // caron
511 { V('a','o'), 1, "˚" }, // ring
512 { V('a','~'), 1, "˜" },
513 { V('h','o'), 1, "˛" }, // ogonek
514 { V('.','i'), 1, "ı" }, // dot less i
515 { V('C','s'), 1, "¤" }, //krazy:exclude=spelling
516 { V('D','o'), 1, "$" },
517 { V('P','o'), 1, "£" },
518 { V('Y','e'), 1, "¥" },
519 { V('F','n'), 1, "ƒ" },
520 { V('F','o'), 1, "«" },
521 { V('F','c'), 1, "»" },
522 { V('f','o'), 1, "‹" }, // single left guillemet
523 { V('f','c'), 1, "›" }, // single right guillemet
524 { V('r','!'), 1, "&iecl;" },
525 { V('r','?'), 1, "¿" },
526 { V('O','f'), 1, "ª" },
527 { V('O','m'), 1, "º" },
528 { V('p','c'), 1, "·" },
529 { V('S','1'), 1, "¹" },
530 { V('S','2'), 1, "²" },
531 { V('S','3'), 1, "³" },
532 { V('<','-'), 1, "←" },
533 { V('-','>'), 1, "→" },
534 { V('<','>'), 1, "↔" },
535 { V('d','a'), 1, "↓" },
536 { V('u','a'), 1, "↑" },
537 { V('l','A'), 1, "⇐" },
538 { V('r','A'), 1, "⇒" },
539 { V('h','A'), 1, "⇔" },
540 { V('d','A'), 1, "⇓" },
541 { V('u','A'), 1, "⇑" },
542 { V('b','a'), 1, "|" },
543 { V('b','b'), 1, "¦" },
544 { V('t','m'), 1, "™" },
545 { V('d','d'), 1, "‡" },
546 { V('p','s'), 1, "¶" },
547 { V('%','0'), 1, "‰" },
548 { V('f','/'), 1, "⁄" }, // Fraction slash
549 { V('s','d'), 1, "″" },
550 { V('h','a'), 1, "^" },
551 { V('t','i'), 1, "&tidle;" },
552 { V('l','B'), 1, "[" },
553 { V('r','B'), 1, "]" },
554 { V('l','C'), 1, "{" },
555 { V('r','C'), 1, "}" },
556 { V('l','a'), 1, "<" },
557 { V('r','a'), 1, ">" },
558 { V('l','h'), 1, "≤" },
559 { V('r','h'), 1, "≥" },
560 { V('B','q'), 1, "„" },
561 { V('b','q'), 1, "‚" },
562 { V('l','q'), 1, "“" },
563 { V('r','q'), 1, "”" },
564 { V('o','q'), 1, "‘" },
565 { V('c','q'), 1, "’" },
566 { V('a','q'), 1, "'" },
567 { V('d','q'), 1, "\"" },
568 { V('a','t'), 1, "@" },
569 { V('s','h'), 1, "#" },
570 { V('r','s'), 1, "\\" },
571 { V('t','f'), 1, "∴" },
572 { V('~','~'), 1, "≅" },
573 { V('~','='), 1, "≈" },
574 { V('!','='), 1, "≠" },
575 { V('<','='), 1, "≤" },
576 { V('=','='), 1, "≡" },
577 { V('=','~'), 1, "≅" }, // ### TODO: verify
578 { V('>','='), 1, "≥" },
579 { V('A','N'), 1, "∧" },
580 { V('O','R'), 1, "∨" },
581 { V('t','e'), 1, "∃" },
582 { V('f','a'), 1, "∀" },
583 { V('A','h'), 1, "ℵ" },
584 { V('I','m'), 1, "ℑ" },
585 { V('R','e'), 1, "ℜ" },
586 { V('i','f'), 1, "∞" },
587 { V('m','d'), 1, "⋅" },
588 { V('m','o'), 1, "∆" }, // element ### TODO verify
589 { V('n','m'), 1, "∉" },
590 { V('p','t'), 1, "∝" },
591 { V('p','p'), 1, "⊥" },
592 { V('s','b'), 1, "⊂" },
593 { V('s','p'), 1, "⊃" },
594 { V('i','b'), 1, "⊆" },
595 { V('i','p'), 1, "⊇" },
596 { V('i','s'), 1, "∫" },
597 { V('s','r'), 1, "√" },
598 { V('p','d'), 1, "∂" },
599 { V('c','*'), 1, "⊗" },
600 { V('c','+'), 1, "⊕" },
601 { V('c','a'), 1, "∩" },
602 { V('c','u'), 1, "∪" },
603 { V('g','r'), 1, "V" }, // gradient ### TODO Where in Unicode?
604 { V('C','R'), 1, "↵" },
605 { V('s','t'), 2, "-)" }, // "such that" ### TODO Where in Unicode?
606 { V('/','_'), 1, "∠" },
607 { V('w','p'), 1, "℘" },
608 { V('l','z'), 1, "◊" },
609 { V('a','n'), 1, "-" }, // "horizontal arrow extension" ### TODO Where in Unicode?
612 /* default: print code */
615 /* static char eqndelimopen=0, eqndelimclose=0; */
616 static char escapesym
='\\', nobreaksym
='\'', controlsym
='.', fieldsym
=0, padsym
=0;
618 static char *buffer
=NULL
;
619 static int buffpos
=0, buffmax
=0;
620 static bool scaninbuff
=false;
621 static int itemdepth
=0;
622 static int section
=0;
623 static int dl_set
[20]= { 0 };
624 static bool still_dd
=0;
625 static int tabstops
[20] = { 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96 };
626 static int maxtstop
=12;
629 static char *scan_troff(char *c
, bool san
, char **result
);
630 static char *scan_troff_mandoc(char *c
, bool san
, char **result
);
632 static QList
<char*> s_argumentList
;
634 static QByteArray cssPath
, cssFile
;
636 static QByteArray s_dollarZero
; // Value of $0
638 void setResourcePath(const QByteArray
& _cssPath
)
643 void setCssFile(const QByteArray
& _cssFile
)
648 static void fill_old_character_definitions( void )
650 for (size_t i
= 0; i
< sizeof(standardchar
)/sizeof(CSTRDEF
); i
++)
652 const int nr
= standardchar
[i
].nr
;
653 const char temp
[3] = { nr
/ 256, nr
% 256, 0 };
654 QByteArray
name( temp
);
655 s_characterDefinitionMap
.insert( name
, StringDefinition( standardchar
[i
].slen
, standardchar
[i
].st
) );
659 static char outbuffer
[NULL_TERMINATED(HUGE_STR_MAX
)];
660 static int no_newline_output
=0;
661 static int newline_for_fun
=0;
662 static bool output_possible
=false;
664 static const char *includedirs
[] = {
667 "/usr/local/include",
668 "/opt/local/include",
670 "/usr/X11R6/include",
671 "/usr/openwin/include",
676 static bool ignore_links
=false;
678 static void add_links(char *c
)
681 ** Add the links to the output.
682 ** At the moment the following are recognized:
684 ** name(*) -> ../man?/name.*
685 ** method://string -> method://string
686 ** www.host.name -> http://www.host.name
687 ** ftp.host.name -> ftp://ftp.host.name
688 ** name@host -> mailto:name@host
689 ** <name.h> -> file:/usr/include/name.h (guess)
691 ** Other possible links to add in the future:
693 ** /dir/dir/file -> file:/dir/dir/file
703 const int numtests
=6; // Nmber of tests
704 char *idtest
[numtests
]; // url, mailto, www, ftp, manpage, C header file
706 /* search for (section) */
708 idtest
[0]=strstr(c
+1,"://");
709 idtest
[1]=strchr(c
+1,'@');
710 idtest
[2]=strstr(c
,"www.");
711 idtest
[3]=strstr(c
,"ftp.");
712 idtest
[4]=strchr(c
+1,'(');
713 idtest
[5]=strstr(c
+1,".h>");
714 for (i
=0; i
<numtests
; ++i
) nr
+= (idtest
[i
]!=NULL
);
717 for (i
=0; i
<numtests
; i
++)
718 if (idtest
[i
] && (j
<0 || idtest
[i
]<idtest
[j
])) j
=i
;
720 case 5: { /* <name.h> */
724 while (g
>c
&& g
[-1]!=';') g
--;
725 bool wrote_include
= false;
730 QByteArray
file(g
, h
- g
+ 1);
731 file
= file
.trimmed();
732 for (int index
= 0; includedirs
[index
]; index
++) {
733 QByteArray
str( includedirs
[index
] );
736 if (!access(str
.data(), R_OK
)) {
737 dir
= includedirs
[index
];
741 if (!dir
.isEmpty()) {
750 str
.append( "<A HREF=\"file:" );
751 str
.append( dir
.data() );
753 str
.append( file
.data() );
755 str
.append( file
.data() );
756 str
.append( "</A>>" );
758 output_real(str
.data());
760 wrote_include
= true;
765 if (!wrote_include
) {
773 case 4: /* manpage */
777 // The character before f must alphanumeric, the end of a HTML tag or the end of a
778 if (g
!=NULL
&& f
>c
&& (g
-f
)<12 && (isalnum(f
[-1]) || f
[-1]=='>' || ( f
[-1] == ';' ) ) &&
779 isdigit(f
[1]) && f
[1]!='0' && ((g
-f
)<=2 || isalpha(f
[2])))
799 kDebug(7107) << "BEFORE SECTION:" << *h
;
800 if ( ( h
> c
+ 5 ) && ( ! memcmp( h
-5, " ", 6 ) ) )
803 kDebug(7107) << "Skip ";
805 else if ( *h
== ';' )
807 // Not a non-breaking space, so probably not ok
814 /* this might be a link */
815 /* skip html makeup */
816 while (h
>c
&& *h
=='>') {
817 while (h
!=c
&& *h
!='<') h
--;
825 const int index
= fstr
.indexOf(')', 2);
828 subsec
= fstr
.mid(2, index
- 2);
829 else // No closing ')' found, take first character as subsection.
830 subsec
= fstr
.mid(2, 1);
831 while (h
>c
&& (isalnum(h
[-1]) || h
[-1]=='_'
832 || h
[-1]==':' || h
[-1]=='-' || h
[-1]=='.'))
840 QByteArray
str("<a href=\"man:");
844 if ( !subsec
.isEmpty() )
845 str
+= subsec
.toLower();
849 output_real(str
.data());
863 while (*g
&& (isalnum(*g
) || *g
=='_' || *g
=='-' || *g
=='+' ||
864 *g
=='.' || *g
=='/')) g
++;
872 str
.append( "<A HREF=\"" );
873 str
.append( j
== 3 ? "ftp" : "http" );
878 str
.append( "</A>" );
879 output_real(str
.data());
891 while (g
>c
&& (isalnum(g
[-1]) || g
[-1]=='_' || g
[-1]=='-' ||
892 g
[-1]=='+' || g
[-1]=='.' || g
[-1]=='%')) g
--;
893 if (g
-7>=c
&& g
[-1]==':')
895 // We have perhaps an email address starting with mailto:
896 if (!qstrncmp("mailto:",g
-7,7))
900 while (*h
&& (isalnum(*h
) || *h
=='_' || *h
=='-' || *h
=='+' ||
903 if (h
-f
>4 && f
-g
>1) {
910 str
.append( "<A HREF=\"mailto:" );
914 str
.append( "</A>" );
915 output_real(str
.data());
927 g
=f
=idtest
[0]; // ://foo...
929 // backup before :// to get protocol
930 while (g
>c
&& isalpha(g
[-1]) && islower(g
[-1])) g
--;
931 h
=f
+3; // start past ://
932 // determine length of path and part of query it looks like...
933 while (*h
&& !isspace(*h
) && *h
!='<' && *h
!='>' && *h
!='"' &&
935 // if protocol length 3-6 characters and path has any length at all...
936 // more tests added because this code breaks stylesheet links that use
937 // the correct file:/// stuff.
938 if (f
-g
>2 && f
-g
<7 && h
-f
>3 && (strstr(c
,"http://") != NULL
|| strstr(c
,"ftp://") != NULL
) ) {
945 str
.append( "<A HREF=\"" );
949 str
.append( "</A>" );
950 output_real(str
.data());
964 if (idtest
[0] && idtest
[0]<=c
) idtest
[0]=strstr(c
+1,"://");
965 if (idtest
[1] && idtest
[1]<=c
) idtest
[1]=strchr(c
+1,'@');
966 if (idtest
[2] && idtest
[2]<c
) idtest
[2]=strstr(c
,"www.");
967 if (idtest
[3] && idtest
[3]<c
) idtest
[3]=strstr(c
,"ftp.");
968 if (idtest
[4] && idtest
[4]<=c
) idtest
[4]=strchr(c
+1,'(');
969 if (idtest
[5] && idtest
[5]<=c
) idtest
[5]=strstr(c
+1,".h>");
970 for (i
=0; i
<numtests
; i
++) nr
+= (idtest
[i
]!=NULL
);
975 static QByteArray current_font
;
976 static int current_size
=0;
977 static int fillout
=1;
979 static void out_html(const char *c
)
983 // Added, probably due to the const?
984 char *c2
= qstrdup(c
);
989 if (no_newline_output
) {
993 if (!no_newline_output
) c2
[i
-1]=c2
[i
];
994 if (c2
[i
]=='\n') no_newline_output
=0;
997 if (!no_newline_output
) c2
[i
-1]=0;
1001 if (buffpos
>=buffmax
) {
1002 char *h
= new char[buffmax
*2];
1004 #ifdef SIMPLE_MAN2HTML
1007 cerr
<< "Memory full, cannot output!" << endl
;
1011 // modern compiler do not return a NULL for a new
1013 memcpy(h
, buffer
, buffmax
);
1018 buffer
[buffpos
++]=*c2
++;
1021 if (output_possible
) {
1023 outbuffer
[obp
++]=*c2
;
1024 if (*c
=='\n' || obp
>= HUGE_STR_MAX
) {
1025 outbuffer
[obp
]='\0';
1026 add_links(outbuffer
);
1035 static QByteArray
set_font( const QByteArray
& name
)
1037 // Every font but R (Regular) creates <span> elements
1039 if ( current_font
!= "R" && !current_font
.isEmpty() )
1040 markup
+= "</span>";
1041 const uint len
= name
.length();
1045 const char lead
= name
[0];
1048 case 'P': // ### TODO: this seems to mean "precedent font"
1049 case 'R': break; // regular, do nothing
1050 case 'I': markup
+= "<span style=\"font-style:italic\">"; break;
1051 case 'B': markup
+= "<span style=\"font-weight:bold\">"; break;
1052 case 'L': markup
+= "<span style=\"font-family:monospace\">"; break; // ### What's L?
1053 default: fontok
= false;
1056 else if ( len
== 2 )
1059 markup
+= "<span style=\"font-style:italic;font-weight:bold\">";
1061 else if ( name
== "CR" )
1062 markup
+= "<span style=\"font-family:monospace\">";
1063 else if ( name
== "CW" ) // CW is used by pod2man(1) (part of perldoc(1))
1064 markup
+= "<span style=\"font-family:monospace\">";
1065 else if ( name
== "CI" )
1066 markup
+= "<span style=\"font-family:monospace;font-style:italic\">";
1067 else if ( name
== "CB" )
1068 markup
+= "<span style=\"font-family:monospace;font-weight:bold\">";
1070 else if ( name
== "TR" )
1071 markup
+= "<span style=\"font-family:serif\">";
1072 else if ( name
== "TI" )
1073 markup
+= "<span style=\"font-family:serif;font-style:italic\">";
1074 else if ( name
== "TB" )
1075 markup
+= "<span style=\"font-family:serif;font-weight:bold\">";
1077 else if ( name
== "HR" )
1078 markup
+= "<span style=\"font-family:sans-serif\">";
1079 else if ( name
== "HI" )
1080 markup
+= "<span style=\"font-family:sans-serif;font-style:italic\">";
1081 else if ( name
== "HB" )
1082 markup
+= "<span style=\"font-family:sans-serif;font-weight:bold\">";
1086 else if ( len
== 3 )
1088 if ( name
== "CBI" )
1089 markup
+= "<span style=\"font-family:monospace;font-style:italic;font-weight:bold\">";
1090 else if ( name
== "TBI" )
1091 markup
+= "<span style=\"font-family:serif;font-style:italic;font-weight:bold\">";
1092 else if ( name
== "HBI" )
1093 markup
+= "<span style=\"font-family:sans-serif;font-style:italic;font-weight:bold\">";
1096 current_font
= name
;
1098 current_font
= "R"; // Still nothing, then it is 'R' (Regular)
1102 static QByteArray
change_to_size(int nr
)
1106 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1107 case '7': case '8': case '9': nr
=nr
-'0'; break;
1109 default: nr
=current_size
+nr
; if (nr
>9) nr
=9; if (nr
< -9) nr
=-9; break;
1111 if ( nr
== current_size
)
1113 const QByteArray
font ( current_font
);
1115 markup
= set_font("R");
1117 markup
+= "</FONT>";
1121 markup
+= "<FONT SIZE=\"";
1129 markup
+= char( nr
+ '0' );
1132 markup
+= set_font( font
);
1136 /* static int asint=0; */
1137 static int intresult
=0;
1139 #define SKIPEOL while (*c && *c++!='\n') {}
1141 static bool skip_escape
=false;
1142 static bool single_escape
=false;
1144 static char *scan_escape_direct( char *c
, QByteArray
& cstr
);
1147 * scan a named character
1150 static QByteArray
scan_named_character( char*& c
)
1155 // \*(ab Name of two characters
1156 if ( c
[1] == escapesym
)
1159 c
= scan_escape_direct( c
+2, cstr
);
1160 // ### HACK: as we convert characters too early to HTML, we need to support more than 2 characters here and assume that all characters passed by the variable are to be used.
1170 else if ( *c
== '[' )
1172 // \*[long_name] Long name
1173 // Named character groff(7)
1174 // We must find the ] to get a name
1176 while ( *c
&& *c
!= ']' && *c
!= '\n' )
1178 if ( *c
== escapesym
)
1181 c
= scan_escape_direct( c
+1, cstr
);
1182 const int result
= cstr
.indexOf(']');
1187 // Note: we drop the characters after the ]
1188 name
+= cstr
.left( result
);
1197 if ( !*c
|| *c
== '\n' )
1199 kDebug(7107) << "Found linefeed! Could not parse character name: " << BYTEARRAY( name
);
1204 else if ( *c
=='C' || c
[1]== '\'' )
1208 while ( *c
&& *c
!= '\'' && *c
!= '\n' )
1210 if ( *c
== escapesym
)
1213 c
= scan_escape_direct( c
+1, cstr
);
1214 const int result
= cstr
.indexOf('\'');
1219 // Note: we drop the characters after the ]
1220 name
+= cstr
.left( result
);
1229 if ( !*c
|| *c
== '\n' )
1231 kDebug(7107) << "Found linefeed! Could not parse (\\C mode) character name: " << BYTEARRAY( name
);
1236 // Note: characters with a one character length name doe not exist, as they would collide with other escapes
1238 // Now we have the name, let us find it between the string names
1239 QMap
<QByteArray
,StringDefinition
>::const_iterator it
=s_characterDefinitionMap
.constFind(name
);
1240 if (it
==s_characterDefinitionMap
.constEnd())
1242 kDebug(7107) << "EXCEPTION: cannot find character with name: " << BYTEARRAY( name
);
1243 // No output, as an undefined string is empty by default
1248 kDebug(7107) << "Character with name: \"" << BYTEARRAY( name
) << "\" => " << BYTEARRAY( (*it
).m_output
);
1249 return (*it
).m_output
;
1253 static QByteArray
scan_named_string(char*& c
)
1258 // \*(ab Name of two characters
1259 if ( c
[1] == escapesym
)
1262 c
= scan_escape_direct( c
+2, cstr
);
1263 kDebug(7107) << "\\(" << BYTEARRAY( cstr
);
1264 // ### HACK: as we convert characters too early to HTML, we need to support more than 2 characters here and assume that all characters passed by the variable are to be used.
1274 else if ( *c
== '[' )
1276 // \*[long_name] Long name
1277 // Named character groff(7)
1278 // We must find the ] to get a name
1280 while ( *c
&& *c
!= ']' && *c
!= '\n' )
1282 if ( *c
== escapesym
)
1285 c
= scan_escape_direct( c
+1, cstr
);
1286 const int result
= cstr
.indexOf(']');
1291 // Note: we drop the characters after the ]
1292 name
+= cstr
.left( result
);
1301 if ( !*c
|| *c
== '\n' )
1303 kDebug(7107) << "Found linefeed! Could not parse string name: " << BYTEARRAY( name
);
1310 // \*a Name of one character
1314 // Now we have the name, let us find it between the string names
1315 QMap
<QByteArray
,StringDefinition
>::const_iterator it
=s_stringDefinitionMap
.constFind(name
);
1316 if (it
==s_stringDefinitionMap
.constEnd())
1318 kDebug(7107) << "EXCEPTION: cannot find string with name: " << BYTEARRAY( name
);
1319 // No output, as an undefined string is empty by default
1324 kDebug(7107) << "String with name: \"" << BYTEARRAY( name
) << "\" => " << BYTEARRAY( (*it
).m_output
);
1325 return (*it
).m_output
;
1329 static QByteArray
scan_dollar_parameter(char*& c
)
1331 int argno
= 0; // No dollar argument number yet!
1334 //kDebug(7107) << "$0";
1336 return s_dollarZero
;
1338 else if ( *c
>= '1' && *c
<= '9' )
1340 //kDebug(7107) << "$ direct";
1341 argno
= ( *c
- '0' );
1344 else if ( *c
== '(' )
1346 //kDebug(7107) << "$(";
1347 if ( c
[1] && c
[2] && c
[1] >= '0' && c
[1] <= '9' && c
[2] >= '0' && c
[2] <= '9' )
1349 argno
= ( c
[1] - '0' ) * 10 + ( c
[2] - '0' );
1363 else if ( *c
== '[' )
1365 //kDebug(7107) << "$[";
1368 while ( *c
&& *c
>='0' && *c
<='9' && *c
!=']' )
1371 argno
+= ( *c
- '0' );
1380 else if ( ( *c
== '*' ) || ( *c
== '@' ) )
1382 const bool quote
= ( *c
== '@' );
1383 QList
<char*>::const_iterator it
= s_argumentList
.constBegin();
1386 for ( ; it
!= s_argumentList
.constEnd(); ++it
)
1391 param
+= '\"'; // Not as HTML, as it could be used by macros !
1394 param
+= '\"'; // Not as HTML, as it could be used by macros!
1402 kDebug(7107) << "EXCEPTION: unknown parameter $" << *c
;
1405 //kDebug(7107) << "ARG $" << argno;
1406 if ( !s_argumentList
.isEmpty() && argno
> 0 )
1408 //kDebug(7107) << "ARG $" << argno << " OK!";
1410 if ( argno
>= s_argumentList
.size() )
1412 kDebug(7107) << "EXCEPTION: cannot find parameter $" << (argno
+1);
1416 return s_argumentList
[argno
];
1421 /// return the value of read-only number registers
1422 static int read_only_number_register( const QByteArray
& name
)
1424 // Internal read-only variables
1427 kDebug(7107) << "\\n[.$] == " << s_argumentList
.size();
1428 return s_argumentList
.size();
1430 else if ( name
== ".g" )
1431 return 0; // We are not groff(1)
1432 else if ( name
== ".s" )
1433 return current_size
;
1435 // ### TODO: map the fonts to a number
1436 else if ( name
== ".f" )
1437 return current_font
;
1439 else if ( name
== ".P" )
1440 return 0; // We are not printing
1441 else if ( name
== ".A" )
1443 #ifndef SIMPLE_MAN2HTML
1444 // Special KDE KIO man:
1445 else if ( name
== ".KDE_VERSION_MAJOR" )
1446 return KDE_VERSION_MAJOR
;
1447 else if ( name
== ".KDE_VERSION_MINOR" )
1448 return KDE_VERSION_MINOR
;
1449 else if ( name
== ".KDE_VERSION_RELEASE" )
1450 return KDE_VERSION_RELEASE
;
1451 else if ( name
== ".KDE_VERSION" )
1454 // ### TODO: should .T be set to "html"? But we are not the HTML post-processor. :-(
1456 // ### TODO: groff defines many more read-only number registers
1457 kDebug(7107) << "EXCEPTION: unknown read-only number register: " << BYTEARRAY( name
);
1459 return 0; // Undefined variable
1463 /// get the value of a number register and auto-increment if asked
1464 static int scan_number_register( char*& c
)
1466 int sign
= 0; // Sign for auto-increment (if any)
1469 case '+': sign
= 1; c
++; break;
1470 case '-': sign
= -1; c
++; break;
1482 else if ( *c
== '-' )
1487 while ( *c
&& *c
!= ']' && *c
!= '\n' )
1489 // ### TODO: a \*[string] could be inside and should be processed
1493 if ( !*c
|| *c
== '\n' )
1495 kDebug(7107) << "Found linefeed! Could not parse number register name: " << BYTEARRAY( name
);
1500 else if ( *c
== '(' )
1508 else if ( *c
== '-' )
1522 if ( name
[0] == '.' )
1524 return read_only_number_register( name
);
1528 QMap
< QByteArray
, NumberDefinition
>::iterator it
= s_numberDefinitionMap
.find( name
);
1529 if ( it
== s_numberDefinitionMap
.end() )
1531 return 0; // Undefined variable
1535 (*it
).m_value
+= sign
* (*it
).m_increment
;
1536 return (*it
).m_value
;
1541 /// get and set font
1542 static QByteArray
scan_named_font( char*& c
)
1547 // \f(ab Name of two characters
1548 if ( c
[1] == escapesym
)
1551 c
= scan_escape_direct( c
+2, cstr
);
1552 kDebug(7107) << "\\(" << BYTEARRAY( cstr
);
1553 // ### HACK: as we convert characters too early to HTML, we need to support more than 2 characters here and assume that all characters passed by the variable are to be used.
1563 else if ( *c
== '[' )
1565 // \f[long_name] Long name
1566 // We must find the ] to get a name
1568 while ( *c
&& *c
!= ']' && *c
!= '\n' )
1570 if ( *c
== escapesym
)
1573 c
= scan_escape_direct( c
+1, cstr
);
1574 const int result
= cstr
.indexOf(']');
1579 // Note: we drop the characters after the ]
1580 name
+= cstr
.left( result
);
1589 if ( !*c
|| *c
== '\n' )
1591 kDebug(7107) << "Found linefeed! Could not parse font name: " << BYTEARRAY( name
);
1598 // \fa Font name with one character or one digit
1599 // ### HACK do *not* use: name = *c; or name would be empty
1603 //kDebug(7107) << "FONT NAME: " << BYTEARRAY( name );
1604 // Now we have the name, let us find the font
1606 const unsigned int number
= name
.toUInt( &ok
);
1611 const char* fonts
[] = { "R", "I", "B", "BI", "CR" }; // Regular, Italic, Bold, Bold Italic, Courier regular
1612 name
= fonts
[ number
];
1616 kDebug(7107) << "EXCEPTION: font has too big number: " << BYTEARRAY( name
) << " => " << number
;
1617 name
= "R"; // Let assume Regular
1620 else if ( name
.isEmpty() )
1622 kDebug(7107) << "EXCEPTION: font has no name: " << BYTEARRAY( name
);
1623 name
= "R"; // Let assume Regular
1626 return set_font( name
);
1631 static QByteArray
scan_number_code( char*& c
)
1636 c
++; // Go past the opening single quote
1637 while ( *c
&& ( *c
!= '\n' ) && ( *c
!= '\'' ) )
1643 unsigned int result
= number
.toUInt( &ok
);
1644 if ( ( result
< ' ' ) || ( result
> 65535 ) )
1646 else if ( result
== '\t' )
1652 number
.setNum( result
);
1653 number
.prepend( "&#" );
1654 number
.append( ";" );
1656 c
++; // Go past the closing single quote
1660 // ### TODO known missing escapes from groff(7):
1661 // ### TODO \& \! \) \: \R
1663 static char *scan_escape_direct( char *c
, QByteArray
& cstr
)
1668 bool cplusplus
= true; // Should the c++ call be executed at the end of the function
1673 case 'e': cstr
= "\\"; curpos
++;break; // ### FIXME: it should be the current escape symbol
1674 case '0': // ### TODO Where in Unicode? (space of digit width)
1675 case '~': // non-breakable-space (resizeable!)
1677 case '|': // half-non-breakable-space
1678 case '^': // quarter-non-breakable-space
1679 cstr
= " "; curpos
++; break;
1680 case '"': SKIPEOL
; c
--; break;
1681 // ### TODO \# like \" but does not ignore the end of line (groff(7))
1685 cstr
= scan_dollar_parameter( c
);
1694 c
=scan_escape_direct( c
+1, cstr
);
1698 cstr
= QByteArray( c
, 1 );
1701 case 'k': c
++; if (*c
=='(') c
+=2; // ### FIXME \k[REG] exists too
1715 // Do not go forward as scan_named_character needs the leading symbol
1716 cstr
= scan_named_character( c
);
1723 cstr
= scan_named_string( c
);
1730 cstr
= scan_named_font( c
);
1734 case 's': // ### FIXME: many forms are missing
1737 if (*c
=='-') {j
= -1; c
++;} else if (*c
=='+') {j
=1; c
++;}
1738 if (*c
=='0') c
++; else if (*c
=='\\') {
1740 c
=scan_escape_direct( c
, cstr
);
1741 i
=intresult
; if (!j
) j
=1;
1743 while (isdigit(*c
) && (!i
|| (!j
&& i
<4))) i
=i
*10+(*c
++)-'0';
1744 if (!j
) { j
=1; if (i
) i
=i
-10; }
1745 if (!skip_escape
) cstr
=change_to_size(i
*j
);
1751 intresult
= scan_number_register( c
);
1759 exoutputp
=output_possible
;
1760 exskipescape
=skip_escape
;
1761 output_possible
=false;
1767 if ( *c
== escapesym
)
1768 c
= scan_escape_direct( c
+1, cstr
);
1772 output_possible
=exoutputp
;
1773 skip_escape
=exskipescape
;
1776 case 'l': cstr
= "<HR>"; curpos
=0;
1786 exoutputp
=output_possible
;
1787 exskipescape
=skip_escape
;
1791 if (*c
==escapesym
) c
=scan_escape_direct( c
+1, cstr
);
1793 output_possible
=exoutputp
;
1794 skip_escape
=exskipescape
;
1796 case 'c': no_newline_output
=1; break;
1797 case '{': newline_for_fun
++; break; // Start conditional block
1798 case '}': if (newline_for_fun
) newline_for_fun
--; break; // End conditional block
1799 case 'p': cstr
= "<BR>\n";curpos
=0; break;
1800 case 't': cstr
= "\t";curpos
=(curpos
+8)&0xfff8; break;
1801 case '<': cstr
= "<";curpos
++; break;
1802 case '>': cstr
= ">";curpos
++; break;
1814 cstr
= scan_number_code( c
);
1818 case '\'': cstr
= "´";curpos
++; break; // groff(7) ### TODO verify
1819 case '`': cstr
= "`";curpos
++; break; // groff(7)
1820 case '-': cstr
= "-";curpos
++; break; // groff(7)
1821 case '.': cstr
= ".";curpos
++; break; // groff(7)
1822 default: cstr
= QByteArray( c
, 1 ); curpos
++; break;
1829 static char *scan_escape(char *c
)
1832 char* result
= scan_escape_direct( c
, cstr
);
1842 TABLEITEM(TABLEROW
*row
);
1846 void setContents(const char *_contents
) {
1848 contents
= qstrdup(_contents
);
1850 const char *getContents() const { return contents
; }
1867 void copyLayout(const TABLEITEM
*orig
) {
1869 align
= orig
->align
;
1870 valign
= orig
->valign
;
1871 colspan
= orig
->colspan
;
1872 rowspan
= orig
->rowspan
;
1874 vleft
= orig
->vleft
;
1875 vright
= orig
->vright
;
1876 space
= orig
->space
;
1877 width
= orig
->width
;
1881 int size
,align
,valign
,colspan
,rowspan
,font
,vleft
,vright
,space
,width
;
1901 int length() const { return items
.count(); }
1902 bool has(int index
) {
1903 return (index
>= 0) && (index
< (int)items
.count());
1905 TABLEITEM
&at(int index
) {
1906 return *items
.at(index
);
1909 TABLEROW
*copyLayout() const;
1911 void addItem(TABLEITEM
*item
) {
1914 TABLEROW
*prev
, *next
;
1917 QList
<TABLEITEM
*> items
;
1920 TABLEITEM::TABLEITEM(TABLEROW
*row
) : contents(0), _parent(row
) {
1922 _parent
->addItem(this);
1925 TABLEROW
*TABLEROW::copyLayout() const {
1926 TABLEROW
*newrow
= new TABLEROW();
1928 QListIterator
<TABLEITEM
*> it(items
);
1929 while (it
.hasNext()){
1930 TABLEITEM
*newitem
= new TABLEITEM(newrow
);
1931 newitem
->copyLayout(it
.next());
1936 static const char *tableopt
[]= { "center", "expand", "box", "allbox",
1937 "doublebox", "tab", "linesize",
1939 static int tableoptl
[] = { 6,6,3,6,9,3,8,5,0};
1942 static void clear_table(TABLEROW
*table
)
1947 while (tr1
->prev
) tr1
=tr1
->prev
;
1955 static char *scan_expression(char *c
, int *result
);
1957 static char *scan_format(char *c
, TABLEROW
**result
, int *maxcol
)
1959 TABLEROW
*layout
, *currow
;
1960 TABLEITEM
*curfield
;
1963 clear_table(*result
);
1965 layout
= currow
=new TABLEROW();
1966 curfield
=new TABLEITEM(currow
);
1967 while (*c
&& *c
!='.') {
1969 case 'C': case 'c': case 'N': case 'n':
1970 case 'R': case 'r': case 'A': case 'a':
1971 case 'L': case 'l': case 'S': case 's':
1973 if (curfield
->align
)
1974 curfield
=new TABLEITEM(currow
);
1975 curfield
->align
=toupper(*c
);
1978 case 'i': case 'I': case 'B': case 'b':
1979 curfield
->font
= toupper(*c
);
1984 curfield
->font
= toupper(*c
);
1986 if (!isspace(*c
) && *c
!='.') c
++;
1988 case 't': case 'T': curfield
->valign
='t'; c
++; break;
1992 if (*c
=='+') { j
=1; c
++; }
1993 if (*c
=='-') { j
=-1; c
++; }
1994 while (isdigit(*c
)) i
=i
*10+(*c
++)-'0';
1995 if (j
) curfield
->size
= i
*j
; else curfield
->size
=j
-10;
1999 c
=scan_expression(c
+2,&curfield
->width
);
2002 if (curfield
->align
) curfield
->vleft
++;
2003 else curfield
->vright
++;
2009 case '0': case '1': case '2': case '3': case '4':
2010 case '5': case '6': case '7': case '8': case '9':
2012 while (isdigit(*c
)) i
=i
*10+(*c
++)-'0';
2015 case ',': case '\n':
2016 currow
->next
=new TABLEROW();
2017 currow
->next
->prev
=currow
;
2018 currow
=currow
->next
;
2020 curfield
=new TABLEITEM(currow
);
2028 if (*c
=='.') while (*c
++!='\n');
2033 if (i
>*maxcol
) *maxcol
=i
;
2034 currow
=currow
->next
;
2040 static TABLEROW
*next_row(TABLEROW
*tr
)
2045 return next_row(tr
);
2048 tr
->next
= tr
->copyLayout();
2049 tr
->next
->prev
= tr
;
2054 static char itemreset
[20]="\\fR\\s0";
2056 #define FORWARDCUR do { curfield++; } while (currow->has(curfield) && currow->at(curfield).align=='S');
2058 static char *scan_table(char *c
)
2062 int center
=0, expand
=0, box
=0, border
=0, linesize
=1;
2063 int i
,j
,maxcol
=0, finished
=0;
2065 int oldsize
,oldfillout
;
2067 TABLEROW
*layout
=NULL
, *currow
;
2071 if (*h
=='.') return c
-1;
2072 oldfont
=current_font
;
2073 oldsize
=current_size
;
2075 out_html(set_font("R"));
2076 out_html(change_to_size(0));
2081 while (*h
&& *h
!='\n') h
++;
2083 /* scan table options */
2085 while (isspace(*c
)) c
++;
2086 for (i
=0; tableopt
[i
] && qstrncmp(tableopt
[i
],c
,tableoptl
[i
]);i
++);
2089 case 0: center
=1; break;
2090 case 1: expand
=1; break;
2091 case 2: box
=1; break;
2092 case 3: border
=1; break;
2093 case 4: box
=2; break;
2094 case 5: while (*c
++!='('); itemsep
=*c
++; break;
2095 case 6: while (*c
++!='('); linesize
=0;
2096 while (isdigit(*c
)) linesize
=linesize
*10+(*c
++)-'0';
2098 case 7: while (*c
!=')') c
++;
2106 c
=scan_format(c
,&layout
, &maxcol
);
2108 currow
=next_row(layout
);
2111 while (!finished
&& *c
) {
2114 if ((*c
=='_' || *c
=='=') && (c
[1]==itemsep
|| c
[1]=='\n')) {
2115 if (c
[-1]=='\n' && c
[1]=='\n') {
2117 currow
->prev
->next
=new TABLEROW();
2118 currow
->prev
->next
->next
=currow
;
2119 currow
->prev
->next
->prev
=currow
->prev
;
2120 currow
->prev
=currow
->prev
->next
;
2122 currow
->prev
=layout
=new TABLEROW();
2123 currow
->prev
->prev
=NULL
;
2124 currow
->prev
->next
=currow
;
2126 TABLEITEM
*newitem
= new TABLEITEM(currow
->prev
);
2128 newitem
->colspan
=maxcol
;
2132 if (currow
->has(curfield
)) {
2133 currow
->at(curfield
).align
=*c
;
2137 currow
=next_row(currow
);
2142 } else if (*c
=='T' && c
[1]=='{') {
2149 scan_troff(itemreset
, 0, &g
);
2152 if (currow
->has(curfield
)) {
2153 currow
->at(curfield
).setContents(g
);
2159 currow
=next_row(currow
);
2162 } else if (*c
=='.' && c
[1]=='T' && c
[2]=='&' && c
[-1]=='\n') {
2166 currow
=currow
->prev
;
2168 c
=scan_format(c
,&hr
, &i
);
2174 } else if (*c
=='.' && c
[1]=='T' && c
[2]=='E' && c
[-1]=='\n') {
2178 currow
->prev
->next
=NULL
;
2180 clear_table(currow
);
2182 } else if (*c
=='.' && c
[-1]=='\n' && !isdigit(c
[1])) {
2183 /* skip troff request inside table (usually only .sp ) */
2187 while (*c
&& (*c
!=itemsep
|| c
[-1]=='\\') &&
2188 (*c
!='\n' || c
[-1]=='\\')) c
++;
2190 if (*c
==itemsep
) {i
=1; *c
='\n'; }
2191 if (h
[0]=='\\' && h
[2]=='\n' &&
2192 (h
[1]=='_' || h
[1]=='^')) {
2193 if (currow
->has(curfield
)) {
2194 currow
->at(curfield
).align
=h
[1];
2200 h
=scan_troff(h
,1,&g
);
2201 scan_troff(itemreset
,0, &g
);
2202 if (currow
->has(curfield
)) {
2203 currow
->at(curfield
).setContents(g
);
2211 currow
=next_row(currow
);
2216 /* calculate colspan and rowspan */
2218 while (currow
->next
) currow
=currow
->next
;
2220 int ti
= 0, ti1
= 0, ti2
= -1;
2221 TABLEROW
*prev
= currow
->prev
;
2225 while (prev
->has(ti1
)) {
2226 if (currow
->has(ti
))
2227 switch (currow
->at(ti
).align
) {
2229 if (currow
->has(ti2
)) {
2230 currow
->at(ti2
).colspan
++;
2231 if (currow
->at(ti2
).rowspan
<prev
->at(ti1
).rowspan
)
2232 currow
->at(ti2
).rowspan
=prev
->at(ti1
).rowspan
;
2236 if (prev
->has(ti1
)) prev
->at(ti1
).rowspan
++;
2238 if (ti2
< 0) ti2
=ti
;
2242 } while (currow
->has(ti2
) && currow
->at(ti2
).align
=='S');
2247 if (ti1
>= 0) ti1
++;
2249 currow
=currow
->prev
;
2251 /* produce html output */
2252 if (center
) out_html("<CENTER>");
2253 if (box
==2) out_html("<TABLE BORDER><TR><TD>");
2255 if (box
|| border
) {
2256 out_html(" BORDER");
2257 if (!border
) out_html("><TR><TD><TABLE");
2258 if (expand
) out_html(" WIDTH=\"100%\"");
2264 out_html("<TR VALIGN=top>");
2266 while (currow
->has(curfield
)) {
2267 if (currow
->at(curfield
).align
!='S' && currow
->at(curfield
).align
!='^') {
2269 switch (currow
->at(curfield
).align
) {
2271 currow
->at(curfield
).space
+=4;
2273 out_html(" ALIGN=right");
2276 out_html(" ALIGN=center");
2280 if (!currow
->at(curfield
).valign
&& currow
->at(curfield
).rowspan
>1)
2281 out_html(" VALIGN=center");
2282 if (currow
->at(curfield
).colspan
>1) {
2284 out_html(" COLSPAN=");
2285 sprintf(buf
, "%i", currow
->at(curfield
).colspan
);
2288 if (currow
->at(curfield
).rowspan
>1) {
2290 out_html(" ROWSPAN=");
2291 sprintf(buf
, "%i", currow
->at(curfield
).rowspan
);
2294 j
=j
+currow
->at(curfield
).colspan
;
2296 if (currow
->at(curfield
).size
) out_html(change_to_size(currow
->at(curfield
).size
));
2297 if (currow
->at(curfield
).font
)
2298 out_html(set_font(QByteArray::number(currow
->at(curfield
).font
) ));
2299 switch (currow
->at(curfield
).align
) {
2300 case '=': out_html("<HR><HR>"); break;
2301 case '_': out_html("<HR>"); break;
2303 out_html(currow
->at(curfield
).getContents());
2306 if (currow
->at(curfield
).space
)
2307 for (i
=0; i
<currow
->at(curfield
).space
;i
++) out_html(" ");
2308 if (currow
->at(curfield
).font
) out_html(set_font("R"));
2309 if (currow
->at(curfield
).size
) out_html(change_to_size(0));
2310 if (j
>=maxcol
&& currow
->at(curfield
).align
>'@' && currow
->at(curfield
).align
!='_')
2316 out_html("</TR>\n");
2317 currow
=currow
->next
;
2320 clear_table(layout
);
2322 if (box
&& !border
) out_html("</TABLE>");
2323 out_html("</TABLE>");
2324 if (box
==2) out_html("</TABLE>");
2325 if (center
) out_html("</CENTER>\n");
2326 else out_html("\n");
2327 if (!oldfillout
) out_html("<PRE>");
2329 out_html(change_to_size(oldsize
));
2330 out_html(set_font(oldfont
));
2334 static char *scan_expression( char *c
, int *result
, const unsigned int numLoop
)
2336 int value
=0,value2
,sign
=1,opex
=0;
2340 c
=scan_expression(c
+1, &value
);
2342 } else if (*c
=='n') {
2345 } else if (*c
=='t') {
2348 } else if (*c
=='\'' || *c
=='"' || *c
<' ' || (*c
=='\\' && c
[1]=='(')) {
2349 /* ?string1?string2?
2350 ** test if string1 equals string2.
2352 char *st1
=NULL
, *st2
=NULL
, *h
;
2362 while (*c
!= sep
&& (!tcmp
|| qstrncmp(c
,tcmp
,4))) c
++;
2364 scan_troff(h
, 1, &st1
);
2369 while (*c
!=sep
&& (!tcmp
|| qstrncmp(c
,tcmp
,4))) c
++;
2371 scan_troff(h
,1,&st2
);
2373 if (!st1
&& !st2
) value
=1;
2374 else if (!st1
|| !st2
) value
=0;
2375 else value
=(!qstrcmp(st1
, st2
));
2381 while (*c
&& ( !isspace(*c
) || ( numLoop
> 0 ) ) && *c
!=')' && opex
>= 0) {
2385 c
= scan_expression( c
+ 1, &value2
, numLoop
+ 1 );
2394 case '8': case '9': {
2397 while (isdigit(*c
)) value2
=value2
*10+((*c
++)-'0');
2398 if (*c
=='.' && isdigit(c
[1])) {
2400 while (isdigit(*c
)) {
2401 num
=num
*10+((*c
++)-'0');
2406 /* scale indicator */
2408 case 'i': /* inch -> 10pt */
2409 value2
=value2
*10+(num
*10+denum
/2)/denum
;
2417 value2
=value2
+(num
+denum
/2)/denum
;
2427 value2
=intresult
*sign
;
2428 if (isalpha(*c
)) c
++; /* scale indicator */
2432 if (oper
) { sign
=-1; c
++; break; }
2442 if (c
[1]=='=') oper
=(*c
++) +16; else oper
=*c
;
2445 default: c
++; break;
2450 case 'c': value
=value2
; break;
2451 case '-': value
=value
-value2
; break;
2452 case '+': value
=value
+value2
; break;
2453 case '*': value
=value
*value2
; break;
2454 case '/': if (value2
) value
=value
/value2
; break;
2455 case '%': if (value2
) value
=value
%value2
; break;
2456 case '<': value
=(value
<value2
); break;
2457 case '>': value
=(value
>value2
); break;
2458 case '>'+16: value
=(value
>=value2
); break;
2459 case '<'+16: value
=(value
<=value2
); break;
2460 case '=': case '='+16: value
=(value
==value2
); break;
2461 case '&': value
= (value
&& value2
); break;
2462 case ':': value
= (value
|| value2
); break;
2465 kDebug(7107) << "Unknown operator " << char(oper
);
2477 static char *scan_expression(char *c
, int *result
)
2479 return scan_expression( c
, result
, 0 );
2482 static void trans_char(char *c
, char s
, char t
)
2486 while (*sl
!='\n' || slash
) {
2497 // 2004-10-19, patched by Waldo Bastian <bastian@kde.org>:
2498 // Fix handling of lines like:
2499 // .TH FIND 1L \" -*- nroff -*-
2500 // Where \" indicates the start of comment.
2502 // The problem is the \" handling in fill_words(), the return value
2503 // indicates the end of the word as well as the end of the line, which makes it
2504 // basically impossible to express that the end of the last word is not the end of
2507 // I have corrected that by adding an extra parameter 'next_line' that returns a
2508 // pointer to the next line, while the function itself returns a pointer to the end
2509 // of the last word.
2510 static char *fill_words(char *c
, char *words
[], int *n
, bool newline
, char **next_line
)
2517 while (*sl
&& (*sl
!='\n' || slash
)) {
2520 if (skipspace
&& (*(sl
+1)=='"'))
2524 skipspace
=!skipspace
;
2526 } else if (*sl
==escapesym
) {
2530 } else if ((*sl
==' ' || *sl
=='\t') && !skipspace
) {
2531 if (newline
) *sl
='\n';
2532 if (words
[*n
]!=sl
) (*n
)++;
2538 if (newline
) *sl
='\n';
2539 if (words
[*n
]!=sl
) (*n
)++;
2544 while (*sl
&& *sl
!='\n') sl
++;
2554 if (sl
!=words
[*n
]) (*n
)++;
2555 if (next_line
) *next_line
= sl
+1;
2559 static const char *abbrev_list
[] = {
2560 "GSBG", "Getting Started ",
2561 "SUBG", "Customizing SunOS",
2562 "SHBG", "Basic Troubleshooting",
2563 "SVBG", "SunView User's Guide",
2564 "MMBG", "Mail and Messages",
2565 "DMBG", "Doing More with SunOS",
2566 "UNBG", "Using the Network",
2567 "GDBG", "Games, Demos & Other Pursuits",
2568 "CHANGE", "SunOS 4.1 Release Manual",
2569 "INSTALL", "Installing SunOS 4.1",
2570 "ADMIN", "System and Network Administration",
2571 "SECUR", "Security Features Guide",
2572 "PROM", "PROM User's Manual",
2573 "DIAG", "Sun System Diagnostics",
2574 "SUNDIAG", "Sundiag User's Guide",
2575 "MANPAGES", "SunOS Reference Manual",
2576 "REFMAN", "SunOS Reference Manual",
2577 "SSI", "Sun System Introduction",
2578 "SSO", "System Services Overview",
2579 "TEXT", "Editing Text Files",
2580 "DOCS", "Formatting Documents",
2581 "TROFF", "Using <B>nroff</B> and <B>troff</B>",
2582 "INDEX", "Global Index",
2583 "CPG", "C Programmer's Guide",
2584 "CREF", "C Reference Manual",
2585 "ASSY", "Assembly Language Reference",
2586 "PUL", "Programming Utilities and Libraries",
2587 "DEBUG", "Debugging Tools",
2588 "NETP", "Network Programming",
2589 "DRIVER", "Writing Device Drivers",
2590 "STREAMS", "STREAMS Programming",
2591 "SBDK", "SBus Developer's Kit",
2592 "WDDS", "Writing Device Drivers for the SBus",
2593 "FPOINT", "Floating-Point Programmer's Guide",
2594 "SVPG", "SunView 1 Programmer's Guide",
2595 "SVSPG", "SunView 1 System Programmer's Guide",
2596 "PIXRCT", "Pixrect Reference Manual",
2597 "CGI", "SunCGI Reference Manual",
2598 "CORE", "SunCore Reference Manual",
2599 "4ASSY", "Sun-4 Assembly Language Reference",
2600 "SARCH", "<FONT SIZE=\"-1\">SPARC</FONT> Architecture Manual",
2601 "KR", "The C Programming Language",
2604 static const char *lookup_abbrev(char *c
)
2609 while (abbrev_list
[i
] && qstrcmp(c
,abbrev_list
[i
])) i
=i
+2;
2610 if (abbrev_list
[i
]) return abbrev_list
[i
+1];
2614 static const char *section_list
[] = {
2617 "1", "User Commands",
2618 "1B", "SunOS/BSD Compatibility Package Commands",
2619 "1b", "SunOS/BSD Compatibility Package Commands",
2620 "1C", "Communication Commands ",
2621 "1c", "Communication Commands",
2622 "1F", "FMLI Commands ",
2623 "1f", "FMLI Commands",
2624 "1G", "Graphics and CAD Commands ",
2625 "1g", "Graphics and CAD Commands ",
2626 "1M", "Maintenance Commands",
2627 "1m", "Maintenance Commands",
2628 "1S", "SunOS Specific Commands",
2629 "1s", "SunOS Specific Commands",
2630 "2", "System Calls",
2631 "3", "C Library Functions",
2632 "3B", "SunOS/BSD Compatibility Library Functions",
2633 "3b", "SunOS/BSD Compatibility Library Functions",
2634 "3C", "C Library Functions",
2635 "3c", "C Library Functions",
2636 "3E", "C Library Functions",
2637 "3e", "C Library Functions",
2638 "3F", "Fortran Library Routines",
2639 "3f", "Fortran Library Routines",
2640 "3G", "C Library Functions",
2641 "3g", "C Library Functions",
2642 "3I", "Wide Character Functions",
2643 "3i", "Wide Character Functions",
2644 "3K", "Kernel VM Library Functions",
2645 "3k", "Kernel VM Library Functions",
2646 "3L", "Lightweight Processes Library",
2647 "3l", "Lightweight Processes Library",
2648 "3M", "Mathematical Library",
2649 "3m", "Mathematical Library",
2650 "3N", "Network Functions",
2651 "3n", "Network Functions",
2652 "3R", "Realtime Library",
2653 "3r", "Realtime Library",
2654 "3S", "Standard I/O Functions",
2655 "3s", "Standard I/O Functions",
2656 "3T", "Threads Library",
2657 "3t", "Threads Library",
2658 "3W", "C Library Functions",
2659 "3w", "C Library Functions",
2660 "3X", "Miscellaneous Library Functions",
2661 "3x", "Miscellaneous Library Functions",
2662 "4", "File Formats",
2663 "4B", "SunOS/BSD Compatibility Package File Formats",
2664 "4b", "SunOS/BSD Compatibility Package File Formats",
2665 "5", "Headers, Tables, and Macros",
2666 "6", "Games and Demos",
2667 "7", "Special Files",
2668 "7B", "SunOS/BSD Compatibility Special Files",
2669 "7b", "SunOS/BSD Compatibility Special Files",
2670 "8", "Maintenance Procedures",
2671 "8C", "Maintenance Procedures",
2672 "8c", "Maintenance Procedures",
2673 "8S", "Maintenance Procedures",
2674 "8s", "Maintenance Procedures",
2676 "9E", "DDI and DKI Driver Entry Points",
2677 "9e", "DDI and DKI Driver Entry Points",
2678 "9F", "DDI and DKI Kernel Functions",
2679 "9f", "DDI and DKI Kernel Functions",
2680 "9S", "DDI and DKI Data Structures",
2681 "9s", "DDI and DKI Data Structures",
2682 "L", "Local Commands",
2683 #elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
2684 "1", "General Commands",
2685 "2", "System Calls",
2686 "3", "Library Functions",
2687 "4", "Kernel Interfaces",
2688 "5", "File Formats",
2690 "7", "Miscellaneous Information",
2691 "8", "System Manager's Manuals",
2692 "9", "Kernel Developer's Manuals",
2695 "1", "User Commands ",
2696 "1C", "User Commands",
2697 "1G", "User Commands",
2698 "1S", "User Commands",
2699 "1V", "User Commands ",
2700 "2", "System Calls",
2701 "2V", "System Calls",
2702 "3", "C Library Functions",
2703 "3C", "Compatibility Functions",
2704 "3F", "Fortran Library Routines",
2705 "3K", "Kernel VM Library Functions",
2706 "3L", "Lightweight Processes Library",
2707 "3M", "Mathematical Library",
2708 "3N", "Network Functions",
2709 "3R", "RPC Services Library",
2710 "3S", "Standard I/O Functions",
2711 "3V", "C Library Functions",
2712 "3X", "Miscellaneous Library Functions",
2713 "4", "Devices and Network Interfaces",
2714 "4F", "Protocol Families",
2715 "4I", "Devices and Network Interfaces",
2716 "4M", "Devices and Network Interfaces",
2717 "4N", "Devices and Network Interfaces",
2719 "4S", "Devices and Network Interfaces",
2720 "4V", "Devices and Network Interfaces",
2721 "5", "File Formats",
2722 "5V", "File Formats",
2723 "6", "Games and Demos",
2724 "7", "Environments, Tables, and Troff Macros",
2725 "7V", "Environments, Tables, and Troff Macros",
2726 "8", "Maintenance Commands",
2727 "8C", "Maintenance Commands",
2728 "8S", "Maintenance Commands",
2729 "8V", "Maintenance Commands",
2730 "L", "Local Commands",
2733 NULL
, "Misc. Reference Manual Pages",
2737 static const char *section_name(char *c
)
2742 while (section_list
[i
] && qstrcmp(c
,section_list
[i
])) i
=i
+2;
2743 if (section_list
[i
+1]) return section_list
[i
+1];
2747 static char *skip_till_newline(char *c
)
2751 while (*c
&& (*c
!='\n' || lvl
>0)) {
2764 if (lvl
<0 && newline_for_fun
) {
2765 newline_for_fun
= newline_for_fun
+lvl
;
2766 if (newline_for_fun
<0) newline_for_fun
=0;
2771 static bool s_whileloop
= false;
2773 /// Processing the .while request
2774 static void request_while( char*& c
, int j
, bool mdoc
)
2776 // ### TODO: .break and .continue
2777 kDebug(7107) << "Entering .while";
2779 char* newline
= skip_till_newline( c
);
2780 const char oldchar
= *newline
;
2782 // We store the full .while stuff into a QCString as if it would be a macro
2783 const QByteArray macro
= c
;
2784 kDebug(7107) << "'Macro' of .while"<< BYTEARRAY( macro
);
2785 // Prepare for continuing after .while loop end
2788 // Process -while loop
2789 const bool oldwhileloop
= s_whileloop
;
2791 int result
= true; // It must be an int due to the call to scan_expression
2794 // Unlike for a normal macro, we have the condition at start, so we do not need to prepend extra bytes
2795 char* liveloop
= qstrdup( macro
.data() );
2796 kDebug(7107) << "Scanning .while condition";
2797 kDebug(7101) << "Loop macro " << liveloop
;
2798 char* end_expression
= scan_expression( liveloop
, &result
);
2799 kDebug(7101) << "After " << end_expression
;
2802 kDebug(7107) << "New .while iteration";
2803 // The condition is true, so call the .while's content
2804 char* help
= end_expression
+ 1;
2805 while ( *help
&& ( *help
== ' ' || *help
== '\t' ) )
2809 // We have a problem, so stop .while
2814 scan_troff_mandoc( help
, false, 0 );
2816 scan_troff( help
, false, 0 );
2822 s_whileloop
= oldwhileloop
;
2823 kDebug(7107) << "Ending .while";
2826 const int max_wordlist
= 100;
2828 /// Processing mixed fonts reqiests like .BI
2829 static void request_mixed_fonts( char*& c
, int j
, const char* font1
, const char* font2
, const bool mode
, const bool inFMode
)
2834 char *wordlist
[max_wordlist
];
2835 fill_words(c
, wordlist
, &words
, true, &c
);
2836 for (int i
=0; i
<words
; i
++)
2838 if ((mode
) || (inFMode
))
2843 wordlist
[i
][-1]=' ';
2844 out_html( set_font( (i
&1) ? font2
: font1
) );
2845 scan_troff(wordlist
[i
],1,NULL
);
2847 out_html(set_font("R"));
2860 // Some known missing requests from man(7):
2861 // - see "safe subset": .tr
2863 // Some known missing requests from mdoc(7):
2864 // - start or end of quotings
2866 // Some of the requests are from mdoc.
2867 // On Linux see the man pages mdoc(7), mdoc.samples(7) and groff_mdoc(7)
2868 // See also the online man pages of FreeBSD: mdoc(7)
2870 #define REQ_UNKNOWN -1
2884 #define REQ_ft 13 // groff(7) "FonT"
2912 #define REQ_IP 41 // man(7) "Indent Paragraph"
2929 #define REQ_SH 58 // man(7) "Sub Header"
2939 #define REQ_nr 68 // groff(7) "Number Register"
2942 #define REQ_Bl 71 // mdoc(7) "Begin List"
2943 #define REQ_El 72 // mdoc(7) "End List"
2944 #define REQ_It 73 // mdoc(7) "ITem"
2948 #define REQ_Os 77 // mdoc(7)
2950 #define REQ_At 79 // mdoc(7) "AT&t" (not parsable, not callable)
2951 #define REQ_Fx 80 // mdoc(7) "Freebsd" (not parsable, not callable)
2954 #define REQ_Bx 83 // mdoc(7) "Bsd"
2955 #define REQ_Ux 84 // mdoc(7) "UniX"
2960 #define REQ_Xr 89 // mdoc(7) "eXternal Reference"
2961 #define REQ_Fl 90 // mdoc(7) "FLag"
2965 #define REQ_Dq 94 // mdoc(7) "Double Quote"
2969 #define REQ_Pq 98 // mdoc(7) "Parenthese Quote"
2971 #define REQ_Sq 100 // mdoc(7) "Single Quote"
2974 #define REQ_Em 103 // mdoc(7) "EMphasis"
2993 #define REQ_perc_A 122
2994 #define REQ_perc_D 123
2995 #define REQ_perc_N 124
2996 #define REQ_perc_O 125
2997 #define REQ_perc_P 126
2998 #define REQ_perc_Q 127
2999 #define REQ_perc_V 128
3000 #define REQ_perc_B 129
3001 #define REQ_perc_J 130
3002 #define REQ_perc_R 131
3003 #define REQ_perc_T 132
3004 #define REQ_An 133 // mdoc(7) "Author Name"
3005 #define REQ_Aq 134 // mdoc(7) "Angle bracket Quote"
3006 #define REQ_Bq 135 // mdoc(7) "Bracket Quote"
3007 #define REQ_Qq 136 // mdoc(7) "straight double Quote"
3008 #define REQ_UR 137 // man(7) "URl"
3009 #define REQ_UE 138 // man(7) "Url End"
3010 #define REQ_UN 139 // man(7) "Url Name" (a.k.a. anchors)
3011 #define REQ_troff 140 // groff(7) "TROFF mode"
3012 #define REQ_nroff 141 // groff(7) "NROFF mode"
3013 #define REQ_als 142 // groff(7) "ALias String"
3014 #define REQ_rr 143 // groff(7) "Remove number Register"
3015 #define REQ_rnn 144 // groff(7) "ReName Number register"
3016 #define REQ_aln 145 // groff(7) "ALias Number register"
3017 #define REQ_shift 146 // groff(7) "SHIFT parameter"
3018 #define REQ_while 147 // groff(7) "WHILE loop"
3019 #define REQ_do 148 // groff(7) "DO command"
3020 #define REQ_Dx 149 // mdoc(7) "DragonFly" macro
3022 static int get_request(char *req
, int len
)
3024 static const char *requests
[] = {
3025 "ab", "di", "ds", "as", "br", "c2", "cc", "ce", "ec", "eo", "ex", "fc",
3026 "fi", "ft", "el", "ie", "if", "ig", "nf", "ps", "sp", "so", "ta", "ti",
3027 "tm", "B", "I", "Fd", "Fn", "Fo", "Fc", "OP", "Ft", "Fa", "BR", "BI",
3028 "IB", "IR", "RB", "RI", "DT", "IP", "TP", "IX", "P", "LP", "PP", "HP",
3029 "PD", "Rs", "RS", "Re", "RE", "SB", "SM", "Ss", "SS", "Sh", "SH", "Sx",
3030 "TS", "Dt", "TH", "TX", "rm", "rn", "nx", "in", "nr", "am", "de", "Bl",
3031 "El", "It", "Bk", "Ek", "Dd", "Os", "Bt", "At", "Fx", "Nx", "Ox", "Bx",
3032 "Ux", "Dl", "Bd", "Ed", "Be", "Xr", "Fl", "Pa", "Pf", "Pp", "Dq", "Op",
3033 "Oo", "Oc", "Pq", "Ql", "Sq", "Ar", "Ad", "Em", "Va", "Xc", "Nd", "Nm",
3034 "Cd", "Cm", "Ic", "Ms", "Or", "Sy", "Dv", "Ev", "Fr", "Li", "No", "Ns",
3035 "Tn", "nN", "%A", "%D", "%N", "%O", "%P", "%Q", "%V", "%B", "%J", "%R",
3036 "%T", "An", "Aq", "Bq", "Qq", "UR", "UE", "UN", "troff", "nroff", "als",
3037 "rr", "rnn", "aln", "shift", "while", "do", "Dx", 0 };
3039 while (requests
[r
] && qstrncmp(req
, requests
[r
], len
)) r
++;
3040 return requests
[r
] ? r
: REQ_UNKNOWN
;
3043 // &%(#@ c programs !!!
3044 //static int ifelseval=0;
3045 // If/else can be nested!
3046 static QStack
<int> s_ifelseval
;
3048 // Process a (mdoc) request involving quotes
3049 static char* process_quote(char* c
, int j
, const char* open
, const char* close
)
3051 trans_char(c
,'"','\a');
3053 if (*c
=='\n') c
++; // ### TODO: why? Quote requests cannot be empty!
3055 c
=scan_troff_mandoc(c
,1,0);
3066 * Is the char \p ch a puntuaction in sence of mdoc(7)
3068 static bool is_mdoc_punctuation( const char ch
)
3070 if ( ( ch
>= '0' && ch
<= '9' ) || ( ch
>='A' && ch
<='Z' ) || ( ch
>= 'a' && ch
<= 'z' ) )
3072 else if ( ch
== '.' || ch
== ',' || ch
== ';' || ch
== ':' || ch
== '(' || ch
== ')'
3073 || ch
== '[' || ch
== ']' )
3080 * Can the char \p c be part of an identifier
3081 * \note For groff, an identifier can consist of nearly all ASCII printable non-white-space characters
3082 * See info:/groff/Identifiers
3084 static bool is_identifier_char( const char c
)
3086 if ( c
>= '!' && c
<= '[' ) // Include digits and upper case
3088 else if ( c
>= ']' && c
<= '~' ) // Include lower case
3090 else if ( c
== '\\' )
3091 return false; // ### TODO: it should be treated as escape instead!
3095 static QByteArray
scan_identifier( char*& c
)
3097 char* h
= c
; // help pointer
3098 // ### TODO Groff seems to eat nearly everything as identifier name (info:/groff/Identifiers)
3099 while ( *h
&& *h
!= '\a' && *h
!= '\n' && is_identifier_char( *h
) )
3101 const char tempchar
= *h
;
3103 const QByteArray name
= c
;
3105 if ( name
.isEmpty() )
3107 kDebug(7107) << "EXCEPTION: identifier empty!";
3113 static char *scan_request(char *c
)
3116 static bool mandoc_synopsis
=false; /* True if we are in the synopsis section */
3117 static bool mandoc_command
=false; /* True if this is mdoc(7) page */
3118 static int mandoc_bd_options
; /* Only copes with non-nested Bd's */
3119 static int function_argument
=0; // Number of function argument (.Fo, .Fa, .Fc)
3121 static bool ur_ignore
=false; // Has .UR a parameter : (for .UE to know if or not to write </a>)
3126 char *wordlist
[max_wordlist
];
3129 while (*c
==' ' || *c
=='\t') c
++; // Spaces or tabs allowed between control character and request
3130 if (c
[0]=='\n') return c
+1;
3131 if (c
[0]==escapesym
)
3133 /* some pages use .\" .\$1 .\} */
3134 /* .\$1 is too difficult/stuppid */
3137 kDebug(7107) << "Found .\\$";
3138 c
=skip_till_newline(c
); // ### TODO
3142 c
= scan_escape(c
+1);
3147 QByteArray macroName
;
3148 while (c
[nlen
] && (c
[nlen
] != ' ') && (c
[nlen
] != '\t') && (c
[nlen
] != '\n') && (c
[nlen
] != escapesym
))
3154 while (c
[j
]==' ' || c
[j
]=='\t') j
++;
3155 /* search macro database of self-defined macros */
3156 QMap
<QByteArray
,StringDefinition
>::const_iterator it
=s_stringDefinitionMap
.constFind(macroName
);
3157 if (it
!=s_stringDefinitionMap
.constEnd())
3159 kDebug(7107) << "CALLING MACRO: " << BYTEARRAY( macroName
);
3160 const QByteArray oldDollarZero
= s_dollarZero
; // Previous value of $0
3161 s_dollarZero
= macroName
;
3162 sl
=fill_words(c
+j
, wordlist
, &words
, true, &c
);
3164 for (i
=1;i
<words
; i
++) wordlist
[i
][-1]='\0';
3165 for (i
=0; i
<words
; i
++)
3169 scan_troff_mandoc(wordlist
[i
],1,&h
);
3171 scan_troff(wordlist
[i
],1,&h
);
3172 wordlist
[i
] = qstrdup(h
);
3175 for ( i
=words
; i
<max_wordlist
; i
++ ) wordlist
[i
]=NULL
;
3176 if ( !(*it
).m_output
.isEmpty() )
3178 //kDebug(7107) << "Macro content is: "<< BYTEARRAY( (*it).m_output );
3179 const unsigned int length
= (*it
).m_output
.length();
3180 char* work
= new char [length
+2];
3181 work
[0] = '\n'; // The macro must start after an end of line to allow a request on first line
3182 qstrncpy(work
+1,(*it
).m_output
.data(),length
+1);
3183 const QList
<char*> oldArgumentList( s_argumentList
);
3184 s_argumentList
.clear();
3185 for ( i
= 0 ; i
< max_wordlist
; i
++ )
3189 s_argumentList
.push_back( wordlist
[i
] );
3191 const int onff
=newline_for_fun
;
3193 scan_troff_mandoc( work
+ 1, 0, NULL
);
3195 scan_troff( work
+ 1, 0, NULL
);
3197 newline_for_fun
=onff
;
3198 s_argumentList
= oldArgumentList
;
3200 for (i
=0; i
<words
; i
++) delete [] wordlist
[i
];
3202 s_dollarZero
= oldDollarZero
;
3203 kDebug(7107) << "ENDING MACRO: " << BYTEARRAY( macroName
);
3207 kDebug(7107) << "REQUEST: " << BYTEARRAY( macroName
);
3208 switch (int request
= get_request(c
, nlen
))
3210 case REQ_ab
: // groff(7) "ABort"
3213 while (*h
&& *h
!='\n') h
++;
3215 if (scaninbuff
&& buffpos
)
3217 buffer
[buffpos
]='\0';
3218 kDebug(7107) << "ABORT: " << buffer
;
3220 // ### TODO find a way to display it to the user
3221 kDebug(7107) << "Aborting: .ab " << (c
+j
);
3225 case REQ_An
: // mdoc(7) "Author Name"
3228 c
=scan_troff_mandoc(c
,1,0);
3231 case REQ_di
: // groff(7) "end current DIversion"
3233 kDebug(7107) << "Start .di";
3240 const QByteArray
name ( scan_identifier( c
) );
3241 while (*c
&& *c
!='\n') c
++;
3244 while (*c
&& qstrncmp(c
,".di",3)) while (*c
&& *c
++!='\n');
3247 scan_troff(h
,0,&result
);
3248 QMap
<QByteArray
,StringDefinition
>::iterator it
=s_stringDefinitionMap
.find(name
);
3249 if (it
==s_stringDefinitionMap
.end())
3251 StringDefinition def
;
3253 def
.m_output
=result
;
3254 s_stringDefinitionMap
.insert(name
,def
);
3259 (*it
).m_output
=result
;
3263 c
=skip_till_newline(c
);
3264 kDebug(7107) << "end .di";
3267 case REQ_ds
: // groff(7) "Define String variable"
3269 case REQ_as
: // groff (7) "Append String variable"
3271 kDebug(7107) << "start .ds/.as";
3272 int oldcurpos
=curpos
;
3274 const QByteArray
name( scan_identifier( c
) );
3275 if ( name
.isEmpty() )
3277 while (*c
&& isspace(*c
)) c
++;
3278 if (*c
&& *c
=='"') c
++;
3282 c
=scan_troff(c
,1,&result
);
3283 QMap
<QByteArray
,StringDefinition
>::iterator it
=s_stringDefinitionMap
.find(name
);
3284 if (it
==s_stringDefinitionMap
.end())
3286 StringDefinition def
;
3287 def
.m_length
=curpos
;
3288 def
.m_output
=result
;
3289 s_stringDefinitionMap
.insert(name
,def
);
3294 { // .ds Defining String
3295 (*it
).m_length
=curpos
;
3296 (*it
).m_output
=result
;
3299 { // .as Appending String
3300 (*it
).m_length
+=curpos
;
3301 (*it
).m_output
+=result
;
3305 single_escape
=false;
3307 kDebug(7107) << "end .ds/.as";
3310 case REQ_br
: // groff(7) "line BReak"
3313 out_html("<DD>"); // ### VERIFY (does not look like generating good HTML)
3318 if (c
[0]==escapesym
) c
=scan_escape(c
+1);
3319 c
=skip_till_newline(c
);
3322 case REQ_c2
: // groff(7) "reset non-break Control character" (2 means non-break)
3329 c
=skip_till_newline(c
);
3332 case REQ_cc
: // groff(7) "reset Control Character"
3339 c
=skip_till_newline(c
);
3342 case REQ_ce
: // groff (7) "CEnter"
3350 while ('0'<=*c
&& *c
<='9')
3356 c
=skip_till_newline(c
);
3357 /* center next i lines */
3360 out_html("<CENTER>\n");
3364 c
=scan_troff(c
,1, &line
);
3365 if (line
&& qstrncmp(line
, "<BR>", 4))
3369 delete [] line
; // ### FIXME: memory leak!
3373 out_html("</CENTER>\n");
3378 case REQ_ec
: // groff(7) "reset Escape Character"
3386 c
=skip_till_newline(c
);
3388 case REQ_eo
: // groff(7) "turn Escape character Off"
3391 c
=skip_till_newline(c
);
3394 case REQ_ex
: // groff(7) "EXit"
3399 case REQ_fc
: // groff(7) "set Field and pad Character"
3403 fieldsym
=padsym
='\0';
3409 c
=skip_till_newline(c
);
3412 case REQ_fi
: // groff(7) "FIll"
3416 out_html(set_font("R"));
3417 out_html(change_to_size('0'));
3418 out_html("</PRE>\n");
3422 c
=skip_till_newline(c
);
3425 case REQ_ft
: // groff(7) "FonT"
3428 h
= skip_till_newline( c
);
3429 const char oldChar
= *h
;
3431 const QByteArray name
= c
;
3432 // ### TODO: name might contain a variable
3433 if ( name
.isEmpty() )
3434 out_html( set_font( "P" ) ); // Previous font
3436 out_html( set_font( name
) );
3441 case REQ_el
: // groff(7) "ELse"
3443 int ifelseval
= s_ifelseval
.pop();
3444 /* .el anything : else part of if else */
3449 c
=scan_troff(c
,1,NULL
);
3452 c
=skip_till_newline(c
+j
);
3455 case REQ_ie
: // groff(7) "If with Else"
3456 /* .ie c anything : then part of if else */
3457 case REQ_if
: // groff(7) "IF"
3463 * .if 'string1'string2' anything
3464 * .if !'string1'string2' anything
3467 c
=scan_expression(c
, &i
);
3468 if (request
== REQ_ie
)
3471 s_ifelseval
.push( ifelseval
);
3477 c
=scan_troff(c
,1,NULL
);
3480 c
=skip_till_newline(c
);
3483 case REQ_ig
: // groff(7) "IGnore"
3485 const char *endwith
="..\n";
3488 if (*c
!='\n' && *c
!= '\\')
3490 /* Not newline or comment */
3493 while (*c
&& *c
!='\n') c
++,i
++;
3496 while (*c
&& qstrncmp(c
,endwith
,i
)) while (*c
++!='\n');
3497 while (*c
&& *c
++!='\n');
3500 case REQ_nf
: // groff(7) "No Filling"
3504 out_html(set_font("R"));
3505 out_html(change_to_size('0'));
3506 out_html("<PRE>\n");
3510 c
=skip_till_newline(c
);
3513 case REQ_ps
: // groff(7) "previous Point Size"
3517 out_html(change_to_size('0'));
3528 c
=scan_expression(c
, &i
);
3534 out_html(change_to_size(i
*j
));
3536 c
=skip_till_newline(c
);
3539 case REQ_sp
: // groff(7) "SKip one line"
3543 out_html("<br><br>");
3549 c
=skip_till_newline(c
);
3552 case REQ_so
: // groff(7) "Include SOurce file"
3567 while (*c
!='\n') c
++;
3569 scan_troff(h
,1, &name
);
3574 /* this works alright, except for section 3 */
3575 buf
=read_man_page(h
);
3578 kDebug(7107) << "Unable to open or read file: .so " << (h
);
3579 out_html("<BLOCKQUOTE>"
3580 "man2html: unable to open or read file.\n");
3582 out_html("</BLOCKQUOTE>\n");
3585 scan_troff(buf
+1,0,NULL
);
3592 case REQ_ta
: // gorff(7) "set TAbulators"
3598 sl
=scan_expression(c
, &tabstops
[j
]);
3599 if (j
>0 && (*c
=='-' || *c
=='+')) tabstops
[j
]+=tabstops
[j
-1];
3601 while (*c
==' ' || *c
=='\t') c
++;
3608 case REQ_ti
: // groff(7) "Temporary Indent"
3610 /*while (itemdepth || dl_set[itemdepth]) {
3611 out_html("</DL>\n");
3612 if (dl_set[itemdepth]) dl_set[itemdepth]=0;
3617 c
=scan_expression(c
, &j
);
3618 for (i
=0; i
<j
; i
++) out_html(" ");
3620 c
=skip_till_newline(c
);
3623 case REQ_tm
: // groff(7) "TerMinal" ### TODO: what are useful uses for it
3627 while (*c
!='\n') c
++;
3629 kDebug(7107) << ".tm " << (h
);
3633 case REQ_B
: // man(7) "Bold"
3635 case REQ_I
: // man(7) "Italic"
3637 /* parse one line in a certain font */
3638 out_html( set_font( mode
?"B":"I" ) );
3639 fill_words(c
, wordlist
, &words
, false, 0);
3642 c
=scan_troff(c
, 1, NULL
);
3643 out_html(set_font("R"));
3651 case REQ_Fd
: // mdoc(7) "Function Definition"
3653 // Normal text must be printed in bold, punctuation in regular font
3655 if (*c
=='\n') c
++; // ### TODO: verify
3656 sl
=fill_words(c
, wordlist
, &words
, true, &c
);
3657 for (i
=0; i
<words
; i
++)
3659 wordlist
[i
][-1]=' ';
3660 // ### FIXME In theory, only a single punctuation character is recognized as punctuation
3661 if ( is_mdoc_punctuation ( *wordlist
[i
] ) )
3662 out_html( set_font ( "R" ) );
3664 out_html( set_font ( "B" ) );
3665 scan_troff(wordlist
[i
],1,NULL
);
3668 // In the mdoc synopsis, there are automatical line breaks (### TODO: before or after?)
3669 if (mandoc_synopsis
)
3673 out_html(set_font("R"));
3681 case REQ_Fn
: // mdoc(7) for "Function calls"
3683 // brackets and commas have to be inserted automatically
3686 sl
=fill_words(c
, wordlist
, &words
, true, &c
);
3689 for (i
=0; i
<words
; i
++)
3691 wordlist
[i
][-1]=' ';
3693 out_html( set_font( "I" ) );
3695 out_html( set_font( "B" ) );
3696 scan_troff(wordlist
[i
],1,NULL
);
3697 out_html( set_font( "R" ) );
3707 out_html(set_font("R"));
3708 if (mandoc_synopsis
)
3717 case REQ_Fo
: // mdoc(7) "Function definition Opening"
3719 char* font
[2] = { (char*)"B", (char*)"R" };
3722 char *eol
=strchr(c
,'\n');
3723 char *semicolon
=strchr(c
,';');
3724 if ((semicolon
!=0) && (semicolon
<eol
)) *semicolon
=' ';
3726 sl
=fill_words(c
, wordlist
, &words
, true, &c
);
3727 // Normally a .Fo has only one parameter
3728 for (i
=0; i
<words
; i
++)
3730 wordlist
[i
][-1]=' ';
3731 out_html(set_font(font
[i
&1]));
3732 scan_troff(wordlist
[i
],1,NULL
);
3737 // ### TODO What should happen if there is more than one argument
3738 // else if (i<words-1) out_html(", ");
3740 function_argument
=1; // Must be > 0
3741 out_html(set_font("R"));
3749 case REQ_Fc
:// mdoc(7) "Function definition Close"
3751 // .Fc has no parameter
3753 c
=skip_till_newline(c
);
3754 char* font
[2] = { (char*)"B", (char*)"R" };
3755 out_html(set_font(font
[i
&1]));
3757 out_html(set_font("R"));
3758 if (mandoc_synopsis
)
3765 function_argument
=0; // Reset the count variable
3768 case REQ_Fa
: // mdoc(7) "Function definition argument"
3770 char* font
[2] = { (char*)"B", (char*)"R" };
3773 sl
=fill_words(c
, wordlist
, &words
, true, &c
);
3774 out_html(set_font(font
[i
&1]));
3775 // function_argument==0 means that we had no .Fo before, e.g. in mdoc.samples(7)
3776 if (function_argument
> 1)
3780 function_argument
++;
3782 else if (function_argument
==1)
3784 // We are only at the first parameter
3785 function_argument
++;
3787 for (i
=0; i
<words
; i
++)
3789 wordlist
[i
][-1]=' ';
3790 scan_troff(wordlist
[i
],1,NULL
);
3792 out_html(set_font("R"));
3800 case REQ_OP
: /* groff manpages use this construction */
3802 /* .OP a b : [ <B>a</B> <I>b</I> ] */
3804 out_html(set_font("R"));
3807 request_mixed_fonts( c
, j
, "B", "I", true, false );
3811 case REQ_Ft
: //perhaps "Function return type"
3813 request_mixed_fonts( c
, j
, "B", "I", false, true );
3818 request_mixed_fonts( c
, j
, "B", "R", false, false );
3823 request_mixed_fonts( c
, j
, "B", "I", false, false );
3828 request_mixed_fonts( c
, j
, "I", "B", false, false );
3833 request_mixed_fonts( c
, j
, "I", "R", false, false );
3838 request_mixed_fonts( c
, j
, "R", "B", false, false );
3843 request_mixed_fonts( c
, j
, "R", "I", false, false );
3846 case REQ_DT
: // man(7) "Default Tabulators"
3848 for (j
=0;j
<20; j
++) tabstops
[j
]=(j
+1)*8;
3850 c
=skip_till_newline(c
);
3853 case REQ_IP
: // man(7) "Ident Paragraph"
3855 sl
=fill_words(c
+j
, wordlist
, &words
, true, &c
);
3856 if (!dl_set
[itemdepth
])
3859 dl_set
[itemdepth
]=1;
3863 scan_troff(wordlist
[0], 1,NULL
);
3868 case REQ_TP
: // man(7) "hanging Tag Paragraph"
3870 if (!dl_set
[itemdepth
])
3872 out_html("<br><br><DL>\n");
3873 dl_set
[itemdepth
]=1;
3876 c
=skip_till_newline(c
);
3877 /* somewhere a definition ends with '.TP' */
3883 while (c
[0]=='.' && c
[1]=='\\' && c
[2]=='\"')
3885 // We have a comment, so skip the line
3886 c
=skip_till_newline(c
);
3888 c
=scan_troff(c
,1,NULL
);
3894 case REQ_IX
: // "INdex" ### TODO: where is it defined?
3897 c
=skip_till_newline(c
);
3900 case REQ_P
: // man(7) "Paragraph"
3901 case REQ_LP
:// man(7) "Paragraph"
3902 case REQ_PP
:// man(7) "Paragraph; reset Prevailing indent"
3904 if (dl_set
[itemdepth
])
3906 out_html("</DL>\n");
3907 dl_set
[itemdepth
]=0;
3910 out_html("<br><br>\n");
3916 c
=skip_till_newline(c
);
3919 case REQ_HP
: // man(7) "Hanging indent Paragraph"
3921 if (!dl_set
[itemdepth
])
3924 dl_set
[itemdepth
]=1;
3928 c
=skip_till_newline(c
);
3932 case REQ_PD
: // man(7) "Paragraph Distance"
3934 c
=skip_till_newline(c
);
3937 case REQ_Rs
: // mdoc(7) "Relative margin Start"
3938 case REQ_RS
: // man(7) "Relative margin Start"
3940 sl
=fill_words(c
+j
, wordlist
, &words
, true, 0);
3942 if (words
>0) scan_expression(wordlist
[0], &j
);
3946 dl_set
[itemdepth
]=0;
3947 out_html("<DL><DT><DD>");
3948 c
=skip_till_newline(c
);
3953 case REQ_Re
: // mdoc(7) "Relative margin End"
3954 case REQ_RE
: // man(7) "Relative margin End"
3958 if (dl_set
[itemdepth
]) out_html("</DL>");
3959 out_html("</DL>\n");
3962 c
=skip_till_newline(c
);
3966 case REQ_SB
: // man(7) "Small; Bold"
3968 out_html(set_font("B"));
3969 out_html("<small>");
3970 trans_char(c
,'"','\a'); // ### VERIFY
3971 c
=scan_troff(c
+j
, 1, NULL
);
3972 out_html("</small>");
3973 out_html(set_font("R"));
3976 case REQ_SM
: // man(7) "SMall"
3980 out_html("<small>");
3981 trans_char(c
,'"','\a'); // ### VERIFY
3982 c
=scan_troff(c
,1,NULL
);
3983 out_html("</small>");
3986 case REQ_Ss
: // mdoc(7) "Sub Section"
3988 case REQ_SS
: // mdoc(7) "Sub Section"
3990 case REQ_Sh
: // mdoc(7) "Sub Header"
3991 /* hack for fallthru from above */
3992 mandoc_command
= !mode
|| mandoc_command
;
3993 case REQ_SH
: // man(7) "Sub Header"
3997 while (itemdepth
|| dl_set
[itemdepth
])
3999 out_html("</DL>\n");
4000 if (dl_set
[itemdepth
])
4001 dl_set
[itemdepth
]=0;
4002 else if (itemdepth
> 0)
4005 out_html(set_font("R"));
4006 out_html(change_to_size(0));
4012 trans_char(c
,'"', '\a');
4015 out_html("</div>\n");
4022 mandoc_synopsis
= qstrncmp(c
, "SYNOPSIS", 8) == 0;
4023 c
= mandoc_command
? scan_troff_mandoc(c
,1,NULL
) : scan_troff(c
,1,NULL
);
4025 out_html("</H3>\n");
4027 out_html("</H2>\n");
4028 out_html("<div>\n");
4034 case REQ_Sx
: // mdoc(7)
4036 // reference to a section header
4037 out_html(set_font("B"));
4038 trans_char(c
,'"','\a');
4041 c
=scan_troff(c
, 1, NULL
);
4042 out_html(set_font("R"));
4050 case REQ_TS
: // Table Start tbl(1)
4055 case REQ_Dt
: /* mdoc(7) */
4056 mandoc_command
= true;
4057 case REQ_TH
: // man(7) "Title Header"
4059 if (!output_possible
)
4061 sl
= fill_words(c
+j
, wordlist
, &words
, true, &c
);
4062 // ### TODO: the page should be displayed even if it is "anonymous" (words==0)
4065 for (i
=1; i
<words
; i
++) wordlist
[i
][-1]='\0';
4067 for (i
=0; i
<words
; i
++)
4069 if (wordlist
[i
][0] == '\007')
4071 if (wordlist
[i
][qstrlen(wordlist
[i
])-1] == '\007')
4072 wordlist
[i
][qstrlen(wordlist
[i
])-1] = 0;
4074 output_possible
=true;
4075 out_html( DOCTYPE
"<HTML>\n<HEAD>\n");
4076 #ifdef SIMPLE_MAN2HTML
4077 // Most English man pages are in ISO-8859-1
4078 out_html("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n");
4080 //let KEncodingDetector decide. (it should be better than charset="System")
4081 //TODO can we check if the charset could be determined from path? like share/man/ru.UTF8
4082 // kio_man transforms from local to UTF-8
4083 // out_html("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=");
4084 // out_html(QTextCodec::codecForLocale()->name());
4085 // out_html("\">\n");
4087 out_html("<TITLE>");
4088 out_html(scan_troff(wordlist
[0], 0, NULL
));
4089 out_html( " Manpage</TITLE>\n");
4092 out_html( "<link rel=\"stylesheet\" href=\"help:/common/kde-default.css\"");
4093 out_html( " type=\"text/css\">\n" );
4095 // Output our custom stylesheet.
4096 out_html( "<link rel=\"stylesheet\" href=\"");
4098 out_html("\" type=\"text/css\">\n" );
4100 // Some elements need background images, but this
4101 // could not be included in the stylesheet,
4103 out_html("<style>\n#header_top { "
4104 "background-image: url(\"help:/common/top.jpg\"); }\n\n"
4105 "#header_top div { "
4106 "background-image: url(\"help:/common/top-left.jpg\"); }\n\n"
4107 "#header_top div div { "
4108 "background-image: url(\"help:/common/top-right.jpg\"); }\n\n"
4112 out_html( "<meta name=\"ROFF Type\" content=\"");
4119 out_html( "</HEAD>\n\n" );
4120 out_html("<BODY>\n\n" );
4122 out_html("<div id=\"header\"><div id=\"header_top\">\n");
4123 out_html("<div><div>\n");
4124 out_html("<img src=\"help:/common/top-kde.jpg\"> ");
4125 out_html( scan_troff(wordlist
[0], 0, NULL
) );
4126 out_html(" - KDE Man Page Viewer");
4127 out_html("</div></div></div></div>\n");
4129 out_html("<div style=\"margin-left: 5em; margin-right: 5em;\">\n");
4131 out_html( scan_troff(wordlist
[0], 0, NULL
) );
4132 out_html( "</h1>\n" );
4135 out_html("Section: " );
4136 if (!mandoc_command
&& words
>4)
4137 out_html(scan_troff(wordlist
[4], 0, NULL
) );
4139 out_html(section_name(wordlist
[1]));
4141 out_html(scan_troff(wordlist
[1], 0, NULL
));
4146 out_html("Section not specified");
4153 kWarning(7107) << ".TH found but output not possible" ;
4154 c
=skip_till_newline(c
);
4159 case REQ_TX
: // mdoc(7)
4161 sl
=fill_words(c
+j
, wordlist
, &words
, true, &c
);
4163 out_html(set_font("I"));
4164 if (words
>1) wordlist
[1][-1]='\0';
4165 const char *c2
=lookup_abbrev(wordlist
[0]);
4166 curpos
+=qstrlen(c2
);
4168 out_html(set_font("R"));
4170 out_html(wordlist
[1]);
4174 case REQ_rm
: // groff(7) "ReMove"
4175 /* .rm xx : Remove request, macro or string */
4177 case REQ_rn
: // groff(7) "ReName"
4178 /* .rn xx yy : Rename request, macro or string xx to yy */
4180 kDebug(7107) << "start .rm/.rn";
4182 const QByteArray
name( scan_identifier( c
) );
4183 if ( name
.isEmpty() )
4185 kDebug(7107) << "EXCEPTION: empty origin string to remove/rename";
4191 while (*c
&& isspace(*c
) && *c
!='\n') ++c
;
4192 name2
= scan_identifier( c
);
4193 if ( name2
.isEmpty() )
4195 kDebug(7107) << "EXCEPTION: empty destination string to rename";
4199 c
=skip_till_newline(c
);
4200 QMap
<QByteArray
,StringDefinition
>::iterator it
=s_stringDefinitionMap
.find(name
);
4201 if (it
==s_stringDefinitionMap
.end())
4203 kDebug(7107) << "EXCEPTION: cannot find string to rename or remove: " << BYTEARRAY( name
);
4210 s_stringDefinitionMap
.remove(name
); // ### QT4: removeAll
4215 StringDefinition def
=(*it
);
4216 s_stringDefinitionMap
.remove(name
); // ### QT4: removeAll
4217 s_stringDefinitionMap
.insert(name2
,def
);
4220 kDebug(7107) << "end .rm/.rn";
4224 case REQ_in
: // groff(7) "INdent"
4226 /* .in +-N : Indent */
4227 c
=skip_till_newline(c
);
4230 case REQ_nr
: // groff(7) "Number Register"
4232 kDebug(7107) << "start .nr";
4234 const QByteArray
name( scan_identifier( c
) );
4235 if ( name
.isEmpty() )
4237 kDebug(7107) << "EXCEPTION: empty name for register variable";
4240 while ( *c
&& ( *c
==' ' || *c
=='\t' ) ) c
++;
4242 if ( *c
&& ( *c
== '+' || *c
== '-' ) )
4246 else if ( *c
== '-' )
4251 c
=scan_expression( c
, &value
);
4252 if ( *c
&& *c
!='\n')
4254 while ( *c
&& ( *c
==' ' || *c
=='\t' ) ) c
++;
4255 c
=scan_expression( c
, &increment
);
4257 c
= skip_till_newline( c
);
4258 QMap
<QByteArray
, NumberDefinition
>::iterator it
= s_numberDefinitionMap
.find( name
);
4259 if ( it
== s_numberDefinitionMap
.end() )
4263 NumberDefinition
def( value
, increment
);
4264 s_numberDefinitionMap
.insert( name
, def
);
4269 (*it
).m_value
+= value
;
4270 else if ( sign
< 0 )
4271 (*it
).m_value
+= - value
;
4273 (*it
).m_value
= value
;
4274 (*it
).m_increment
= increment
;
4276 kDebug(7107) << "end .nr";
4279 case REQ_am
: // groff(7) "Append Macro"
4280 /* .am xx yy : append to a macro. */
4281 /* define or handle as .ig yy */
4283 case REQ_de
: // groff(7) "DEfine macro"
4284 /* .de xx yy : define or redefine macro xx; end at .yy (..) */
4285 /* define or handle as .ig yy */
4287 kDebug(7107) << "Start .am/.de";
4290 sl
= fill_words(c
, wordlist
, &words
, true, &next_line
);
4291 char *nameStart
= wordlist
[0];
4293 while (*c
&& (*c
!= ' ') && (*c
!= '\n')) c
++;
4295 const QByteArray
name(nameStart
);
4297 QByteArray endmacro
;
4306 while (*c
&& (*c
!= ' ') && (*c
!= '\n'))
4311 const int length
=qstrlen(endmacro
);
4312 while (*c
&& qstrncmp(c
,endmacro
,length
))
4313 c
=skip_till_newline(c
);
4318 if (sl
[0]=='\\' && sl
[1]=='\\')
4328 QMap
<QByteArray
,StringDefinition
>::iterator it
=s_stringDefinitionMap
.find(name
);
4329 if (it
==s_stringDefinitionMap
.end())
4331 StringDefinition def
;
4334 s_stringDefinitionMap
.insert(name
,def
);
4339 (*it
).m_length
=0; // It could be formerly a string
4340 if ( ! (*it
).m_output
.endsWith( '\n' ) )
4341 (*it
).m_output
+='\n';
4342 (*it
).m_output
+=macro
;
4347 (*it
).m_length
=0; // It could be formerly a string
4348 (*it
).m_output
=macro
;
4350 c
=skip_till_newline(c
);
4351 kDebug(7107) << "End .am/.de";
4354 case REQ_Bl
: // mdoc(7) "Begin List"
4356 char list_options
[NULL_TERMINATED(MED_STR_MAX
)];
4357 char *nl
= strchr(c
,'\n');
4359 if (dl_set
[itemdepth
])
4360 /* These things can nest. */
4364 /* Parse list options */
4365 strlimitcpy(list_options
, c
, nl
- c
, MED_STR_MAX
);
4367 if (strstr(list_options
, "-bullet"))
4369 /* HTML Unnumbered List */
4370 dl_set
[itemdepth
] = BL_BULLET_LIST
;
4373 else if (strstr(list_options
, "-enum"))
4375 /* HTML Ordered List */
4376 dl_set
[itemdepth
] = BL_ENUM_LIST
;
4381 /* HTML Descriptive List */
4382 dl_set
[itemdepth
] = BL_DESC_LIST
;
4386 out_html("<br><br>\n");
4392 c
=skip_till_newline(c
);
4395 case REQ_El
: // mdoc(7) "End List"
4398 if (dl_set
[itemdepth
] & BL_DESC_LIST
)
4399 out_html("</DL>\n");
4400 else if (dl_set
[itemdepth
] & BL_BULLET_LIST
)
4401 out_html("</UL>\n");
4402 else if (dl_set
[itemdepth
] & BL_ENUM_LIST
)
4403 out_html("</OL>\n");
4404 dl_set
[itemdepth
]=0;
4405 if (itemdepth
> 0) itemdepth
--;
4407 out_html("<br><br>\n");
4413 c
=skip_till_newline(c
);
4416 case REQ_It
: // mdoc(7) "list ITem"
4419 if (qstrncmp(c
, "Xo", 2) == 0 && isspace(*(c
+2)))
4420 c
= skip_till_newline(c
);
4421 if (dl_set
[itemdepth
] & BL_DESC_LIST
)
4424 out_html(set_font("B"));
4427 /* Don't allow embedded comms after a newline */
4429 c
=scan_troff(c
,1,NULL
);
4433 /* Do allow embedded comms on the same line. */
4434 c
=scan_troff_mandoc(c
,1,NULL
);
4436 out_html(set_font("R"));
4440 else if (dl_set
[itemdepth
] & (BL_BULLET_LIST
| BL_ENUM_LIST
))
4443 c
=scan_troff_mandoc(c
,1,NULL
);
4452 case REQ_Bk
: /* mdoc(7) */
4453 case REQ_Ek
: /* mdoc(7) */
4454 case REQ_Dd
: /* mdoc(7) */
4455 case REQ_Os
: // mdoc(7) "Operating System"
4457 trans_char(c
,'"','\a');
4460 c
=scan_troff_mandoc(c
, 1, NULL
);
4468 case REQ_Bt
: // mdoc(7) "Beta Test"
4470 trans_char(c
,'"','\a');
4472 out_html(" is currently in beta test.");
4479 case REQ_At
: /* mdoc(7) */
4480 case REQ_Fx
: /* mdoc(7) */
4481 case REQ_Nx
: /* mdoc(7) */
4482 case REQ_Ox
: /* mdoc(7) */
4483 case REQ_Bx
: /* mdoc(7) */
4484 case REQ_Ux
: /* mdoc(7) */
4485 case REQ_Dx
: /* mdoc(7) */
4488 trans_char(c
,'"','\a');
4491 if (request
==REQ_At
)
4493 out_html("AT&T UNIX ");
4496 else if (request
==REQ_Fx
)
4498 out_html("FreeBSD ");
4501 else if (request
==REQ_Nx
)
4502 out_html("NetBSD ");
4503 else if (request
==REQ_Ox
)
4504 out_html("OpenBSD ");
4505 else if (request
==REQ_Bx
)
4507 else if (request
==REQ_Ux
)
4509 else if (request
==REQ_Dx
)
4510 out_html("DragonFly ");
4512 c
=scan_troff_mandoc(c
,1,0);
4514 c
=scan_troff(c
,1,0);
4521 case REQ_Dl
: /* mdoc(7) */
4525 out_html("<BLOCKQUOTE>");
4527 c
=scan_troff_mandoc(c
, 1, NULL
);
4528 out_html("</BLOCKQUOTE>");
4535 case REQ_Bd
: /* mdoc(7) */
4536 { /* Seems like a kind of example/literal mode */
4537 char bd_options
[NULL_TERMINATED(MED_STR_MAX
)];
4538 char *nl
= strchr(c
,'\n');
4541 strlimitcpy(bd_options
, c
, nl
- c
, MED_STR_MAX
);
4543 mandoc_bd_options
= 0; /* Remember options for terminating Bl */
4544 if (strstr(bd_options
, "-offset indent"))
4546 mandoc_bd_options
|= BD_INDENT
;
4547 out_html("<BLOCKQUOTE>\n");
4549 if ( strstr(bd_options
, "-literal") || strstr(bd_options
, "-unfilled"))
4553 mandoc_bd_options
|= BD_LITERAL
;
4554 out_html(set_font("R"));
4555 out_html(change_to_size('0'));
4556 out_html("<PRE>\n");
4561 c
=skip_till_newline(c
);
4564 case REQ_Ed
: /* mdoc(7) */
4566 if (mandoc_bd_options
& BD_LITERAL
)
4570 out_html(set_font("R"));
4571 out_html(change_to_size('0'));
4572 out_html("</PRE>\n");
4575 if (mandoc_bd_options
& BD_INDENT
)
4576 out_html("</BLOCKQUOTE>\n");
4579 c
=skip_till_newline(c
);
4582 case REQ_Be
: /* mdoc(7) */
4586 out_html("<br><br>");
4592 c
=skip_till_newline(c
);
4595 case REQ_Xr
: /* mdoc(7) */ // ### FIXME: it should issue a <a href="man:somewhere(x)"> directly
4597 /* Translate xyz 1 to xyz(1)
4598 * Allow for multiple spaces. Allow the section to be missing.
4600 char buff
[NULL_TERMINATED(MED_STR_MAX
)];
4602 trans_char(c
,'"','\a');
4605 if (*c
== '\n') c
++; /* Skip spaces */
4606 while (isspace(*c
) && *c
!= '\n') c
++;
4607 while (isalnum(*c
) || *c
== '.' || *c
== ':' || *c
== '_' || *c
== '-')
4609 /* Copy the xyz part */
4612 if (bufptr
>= buff
+ MED_STR_MAX
) break;
4615 while (isspace(*c
) && *c
!= '\n') c
++; /* Skip spaces */
4618 /* Convert the number if there is one */
4621 if (bufptr
< buff
+ MED_STR_MAX
)
4627 if (bufptr
>= buff
+ MED_STR_MAX
) break;
4630 if (bufptr
< buff
+ MED_STR_MAX
)
4639 /* Copy the remainder */
4644 if (bufptr
>= buff
+ MED_STR_MAX
) break;
4650 scan_troff_mandoc(buff
, 1, NULL
);
4658 case REQ_Fl
: // mdoc(7) "FLags"
4660 trans_char(c
,'"','\a');
4662 sl
=fill_words(c
, wordlist
, &words
, true, &c
);
4663 out_html(set_font("B"));
4666 out_html("-"); // stdin or stdout
4670 for (i
=0;i
<words
;++i
)
4672 if (ispunct(wordlist
[i
][0]) && wordlist
[i
][0]!='-')
4674 scan_troff_mandoc(wordlist
[i
], 1, NULL
);
4679 out_html(" "); // Put a space between flags
4681 scan_troff_mandoc(wordlist
[i
], 1, NULL
);
4685 out_html(set_font("R"));
4693 case REQ_Pa
: /* mdoc(7) */
4694 case REQ_Pf
: /* mdoc(7) */
4696 trans_char(c
,'"','\a');
4699 c
=scan_troff_mandoc(c
, 1, NULL
);
4707 case REQ_Pp
: /* mdoc(7) */
4710 out_html("<br><br>\n");
4716 c
=skip_till_newline(c
);
4719 case REQ_Aq
: // mdoc(7) "Angle bracket Quote"
4720 c
=process_quote(c
,j
,"<",">");
4722 case REQ_Bq
: // mdoc(7) "Bracket Quote"
4723 c
=process_quote(c
,j
,"[","]");
4725 case REQ_Dq
: // mdoc(7) "Double Quote"
4726 c
=process_quote(c
,j
,"“","”");
4728 case REQ_Pq
: // mdoc(7) "Parenthese Quote"
4729 c
=process_quote(c
,j
,"(",")");
4731 case REQ_Qq
: // mdoc(7) "straight double Quote"
4732 c
=process_quote(c
,j
,""",""");
4734 case REQ_Sq
: // mdoc(7) "Single Quote"
4735 c
=process_quote(c
,j
,"‘","’");
4737 case REQ_Op
: /* mdoc(7) */
4739 trans_char(c
,'"','\a');
4742 out_html(set_font("R"));
4744 c
=scan_troff_mandoc(c
, 1, NULL
);
4745 out_html(set_font("R"));
4754 case REQ_Oo
: /* mdoc(7) */
4756 trans_char(c
,'"','\a');
4759 out_html(set_font("R"));
4761 c
=scan_troff_mandoc(c
, 1, NULL
);
4768 case REQ_Oc
: /* mdoc(7) */
4770 trans_char(c
,'"','\a');
4772 c
=scan_troff_mandoc(c
, 1, NULL
);
4773 out_html(set_font("R"));
4781 case REQ_Ql
: /* mdoc(7) */
4783 /* Single quote first word in the line */
4785 trans_char(c
,'"','\a');
4791 /* Find first whitespace after the
4792 * first word that isn't a mandoc macro
4794 while (*sp
&& isspace(*sp
)) sp
++;
4795 while (*sp
&& !isspace(*sp
)) sp
++;
4796 } while (*sp
&& isupper(*(sp
-2)) && islower(*(sp
-1)));
4798 /* Use a newline to mark the end of text to
4801 if (*sp
) *sp
= '\n';
4802 out_html("`"); /* Quote the text */
4803 c
=scan_troff_mandoc(c
, 1, NULL
);
4812 case REQ_Ar
: /* mdoc(7) */
4814 /* parse one line in italics */
4815 out_html(set_font("I"));
4816 trans_char(c
,'"','\a');
4820 /* An empty Ar means "file ..." */
4821 out_html("file ...");
4824 c
=scan_troff_mandoc(c
, 1, NULL
);
4825 out_html(set_font("R"));
4833 case REQ_Em
: /* mdoc(7) */
4836 trans_char(c
,'"','\a');
4839 c
=scan_troff_mandoc(c
, 1, NULL
);
4848 case REQ_Ad
: /* mdoc(7) */
4849 case REQ_Va
: /* mdoc(7) */
4850 case REQ_Xc
: /* mdoc(7) */
4852 /* parse one line in italics */
4853 out_html(set_font("I"));
4854 trans_char(c
,'"','\a');
4857 c
=scan_troff_mandoc(c
, 1, NULL
);
4858 out_html(set_font("R"));
4866 case REQ_Nd
: /* mdoc(7) */
4868 trans_char(c
,'"','\a');
4872 c
=scan_troff_mandoc(c
, 1, NULL
);
4880 case REQ_Nm
: // mdoc(7) "Name Macro" ### FIXME
4882 static char mandoc_name
[NULL_TERMINATED(SMALL_STR_MAX
)] = ""; // ### TODO Use QByteArray
4883 trans_char(c
,'"','\a');
4886 if (mandoc_synopsis
&& mandoc_name_count
)
4888 /* Break lines only in the Synopsis.
4889 * The Synopsis section seems to be treated
4890 * as a special case - Bummer!
4894 else if (!mandoc_name_count
)
4896 const char *nextbreak
= strchr(c
, '\n');
4897 const char *nextspace
= strchr(c
, ' ');
4898 if (nextspace
< nextbreak
)
4899 nextbreak
= nextspace
;
4903 /* Remember the name for later. */
4904 strlimitcpy(mandoc_name
, c
, nextbreak
- c
, SMALL_STR_MAX
);
4907 mandoc_name_count
++;
4909 out_html(set_font("B"));
4910 // ### FIXME: fill_words must be used
4911 while (*c
== ' '|| *c
== '\t') c
++;
4912 if ((tolower(*c
) >= 'a' && tolower(*c
) <= 'z' ) || (*c
>= '0' && *c
<= '9'))
4914 // alphanumeric argument
4915 c
=scan_troff_mandoc(c
, 1, NULL
);
4916 out_html(set_font("R"));
4921 /* If Nm has no argument, use one from an earlier
4922 * Nm command that did have one. Hope there aren't
4923 * too many commands that do this.
4925 out_html(mandoc_name
);
4926 out_html(set_font("R"));
4935 case REQ_Cd
: /* mdoc(7) */
4936 case REQ_Cm
: /* mdoc(7) */
4937 case REQ_Ic
: /* mdoc(7) */
4938 case REQ_Ms
: /* mdoc(7) */
4939 case REQ_Or
: /* mdoc(7) */
4940 case REQ_Sy
: /* mdoc(7) */
4942 /* parse one line in bold */
4943 out_html(set_font("B"));
4944 trans_char(c
,'"','\a');
4947 c
=scan_troff_mandoc(c
, 1, NULL
);
4948 out_html(set_font("R"));
4956 // ### FIXME: punctuation is handled badly!
4957 case REQ_Dv
: /* mdoc(7) */
4958 case REQ_Ev
: /* mdoc(7) */
4959 case REQ_Fr
: /* mdoc(7) */
4960 case REQ_Li
: /* mdoc(7) */
4961 case REQ_No
: /* mdoc(7) */
4962 case REQ_Ns
: /* mdoc(7) */
4963 case REQ_Tn
: /* mdoc(7) */
4964 case REQ_nN
: /* mdoc(7) */
4966 trans_char(c
,'"','\a');
4969 out_html(set_font("B"));
4970 c
=scan_troff_mandoc(c
, 1, NULL
);
4971 out_html(set_font("R"));
4979 case REQ_perc_A
: /* mdoc(7) biblio stuff */
4989 c
=scan_troff(c
, 1, NULL
); /* Don't allow embedded mandoc coms */
5002 out_html(set_font("I"));
5004 c
=scan_troff(c
, 1, NULL
); /* Don't allow embedded mandoc coms */
5005 out_html(set_font("R"));
5012 case REQ_UR
: // ### FIXME man(7) "URl"
5017 h
=fill_words(c
, wordlist
, &words
, false, &newc
);
5022 // A parameter : means that we do not want an URL, not here and not until .UE
5023 ur_ignore
=(!qstrcmp(h
,":"));
5027 // We cannot find the URL, assume :
5031 if (!ur_ignore
&& words
>0)
5033 out_html("<a href=\"");
5037 c
=newc
; // Go to next line
5040 case REQ_UE
: // ### FIXME man(7) "Url End"
5043 c
= skip_till_newline(c
);
5052 case REQ_UN
: // ### FIXME man(7) "Url Named anchor"
5056 h
=fill_words(c
, wordlist
, &words
, false, &newc
);
5061 out_html("<a name=\">");
5063 out_html("\" id=\"");
5065 out_html("\"></a>");
5070 case REQ_nroff
: // groff(7) "NROFF mode"
5072 case REQ_troff
: // groff(7) "TROFF mode"
5076 c
= skip_till_newline(c
);
5078 case REQ_als
: // groff(7) "ALias String"
5081 * Note an alias is supposed to be something like a hard link
5082 * However to make it simplier, we only copy the string.
5084 // Be careful: unlike .rn, the destination is first, origin is second
5085 kDebug(7107) << "start .als";
5087 const QByteArray
name ( scan_identifier( c
) );
5088 if ( name
.isEmpty() )
5090 kDebug(7107) << "EXCEPTION: empty destination string to alias";
5093 while (*c
&& isspace(*c
) && *c
!='\n') ++c
;
5094 const QByteArray
name2 ( scan_identifier ( c
) );
5095 if ( name2
.isEmpty() )
5097 kDebug(7107) << "EXCEPTION: empty origin string to alias";
5100 kDebug(7107) << "Alias " << BYTEARRAY( name2
) << " to " << BYTEARRAY( name
);
5101 c
=skip_till_newline(c
);
5102 if ( name
== name2
)
5104 kDebug(7107) << "EXCEPTION: same origin and destination string to alias: " << BYTEARRAY( name
);
5107 // Second parameter is origin (unlike in .rn)
5108 QMap
<QByteArray
,StringDefinition
>::iterator it
=s_stringDefinitionMap
.find(name2
);
5109 if (it
==s_stringDefinitionMap
.end())
5111 kDebug(7107) << "EXCEPTION: cannot find string to make alias of " << BYTEARRAY( name2
);
5115 StringDefinition def
=(*it
);
5116 s_stringDefinitionMap
.insert(name
,def
);
5118 kDebug(7107) << "end .als";
5121 case REQ_rr
: // groff(7) "Remove number Register"
5123 kDebug(7107) << "start .rr";
5125 const QByteArray
name ( scan_identifier( c
) );
5126 if ( name
.isEmpty() )
5128 kDebug(7107) << "EXCEPTION: empty origin string to remove/rename: ";
5131 c
= skip_till_newline( c
);
5132 QMap
<QByteArray
, NumberDefinition
>::iterator it
= s_numberDefinitionMap
.find( name
);
5133 if ( it
== s_numberDefinitionMap
.end() )
5135 kDebug(7107) << "EXCEPTION: trying to remove inexistant number register: ";
5139 s_numberDefinitionMap
.remove( name
);
5141 kDebug(7107) << "end .rr";
5144 case REQ_rnn
: // groff(7) "ReName Number register"
5146 kDebug(7107) << "start .rnn";
5148 const QByteArray
name ( scan_identifier ( c
) );
5149 if ( name
.isEmpty() )
5151 kDebug(7107) << "EXCEPTION: empty origin to remove/rename number register";
5154 while (*c
&& isspace(*c
) && *c
!='\n') ++c
;
5155 const QByteArray
name2 ( scan_identifier ( c
) );
5156 if ( name2
.isEmpty() )
5158 kDebug(7107) << "EXCEPTION: empty destination to rename number register";
5161 c
= skip_till_newline( c
);
5162 QMap
<QByteArray
,NumberDefinition
>::iterator it
=s_numberDefinitionMap
.find(name
);
5163 if (it
==s_numberDefinitionMap
.end())
5165 kDebug(7107) << "EXCEPTION: cannot find number register to rename" << BYTEARRAY( name
);
5169 NumberDefinition def
=(*it
);
5170 s_numberDefinitionMap
.remove(name
); // ### QT4: removeAll
5171 s_numberDefinitionMap
.insert(name2
,def
);
5173 kDebug(7107) << "end .rnn";
5176 case REQ_aln
: // groff(7) "ALias Number Register"
5179 * Note an alias is supposed to be something like a hard link
5180 * However to make it simplier, we only copy the string.
5182 // Be careful: unlike .rnn, the destination is first, origin is second
5183 kDebug(7107) << "start .aln";
5185 const QByteArray
name ( scan_identifier( c
) );
5186 if ( name
.isEmpty() )
5188 kDebug(7107) << "EXCEPTION: empty destination number register to alias";
5191 while (*c
&& isspace(*c
) && *c
!='\n') ++c
;
5192 const QByteArray
name2 ( scan_identifier( c
) );
5193 if ( name2
.isEmpty() )
5195 kDebug(7107) << "EXCEPTION: empty origin number register to alias";
5198 kDebug(7107) << "Alias " << BYTEARRAY( name2
) << " to " << BYTEARRAY( name
);
5199 c
= skip_till_newline( c
);
5200 if ( name
== name2
)
5202 kDebug(7107) << "EXCEPTION: same origin and destination number register to alias: " << BYTEARRAY( name
);
5205 // Second parameter is origin (unlike in .rnn)
5206 QMap
<QByteArray
,NumberDefinition
>::iterator it
=s_numberDefinitionMap
.find(name2
);
5207 if (it
==s_numberDefinitionMap
.end())
5209 kDebug(7107) << "EXCEPTION: cannot find string to make alias: " << BYTEARRAY( name2
);
5213 NumberDefinition def
=(*it
);
5214 s_numberDefinitionMap
.insert(name
,def
);
5216 kDebug(7107) << "end .aln";
5219 case REQ_shift
: // groff(7) "SHIFT parameter"
5223 while (*h
&& *h
!='\n' && isdigit(*h
) ) ++h
;
5224 const char tempchar
= *h
;
5226 const QByteArray
number( c
);
5228 c
= skip_till_newline( h
);
5229 unsigned int result
= 1; // Numbers of shifts to do
5230 if ( !number
.isEmpty() )
5233 result
= number
.toUInt(&ok
);
5234 if ( !ok
|| result
< 1 )
5237 for ( unsigned int num
= 0; num
< result
; ++num
)
5239 if ( !s_argumentList
.isEmpty() )
5240 s_argumentList
.pop_front();
5244 case REQ_while
: // groff(7) "WHILE loop"
5246 request_while( c
, j
, mandoc_command
);
5249 case REQ_do
: // groff(7) "DO command"
5251 // ### HACK: we just replace do by a \n and a .
5255 // The . will be treated as next character
5260 if (mandoc_command
&&
5261 ((isupper(*c
) && islower(*(c
+1)))
5262 || (islower(*c
) && isupper(*(c
+1)))) )
5264 /* Let through any mdoc(7) commands that haven't
5266 * I don't want to miss anything out of the text.
5268 char buf
[4] = { c
[0], c
[1], ' ', 0 };
5269 out_html(buf
); /* Print the command (it might just be text). */
5271 trans_char(c
,'"','\a');
5273 out_html(set_font("R"));
5274 c
=scan_troff(c
, 1, NULL
);
5282 c
=skip_till_newline(c
);
5296 static int contained_tab
=0;
5297 static bool mandoc_line
=false; /* Signals whether to look for embedded mandoc
5301 static char *scan_troff(char *c
, bool san
, char **result
)
5302 { /* san : stop at newline */
5304 char intbuff
[NULL_TERMINATED(MED_STR_MAX
)];
5306 #define FLUSHIBP if (ibp) { intbuff[ibp]=0; out_html(intbuff); ibp=0; }
5308 int exbuffpos
, exbuffmax
, exnewline_for_fun
;
5315 exnewline_for_fun
=newline_for_fun
;
5316 exscaninbuff
=scaninbuff
;
5321 buffpos
=qstrlen(buffer
);
5324 buffer
= stralloc(LARGE_STR_MAX
);
5326 buffmax
=LARGE_STR_MAX
;
5330 h
=c
; // ### FIXME below are too many tests that may go before the position of c
5331 /* start scanning */
5333 // ### VERIFY: a dot must be at first position, we cannot add newlines or it would allow spaces before a dot
5343 while (h
&& *h
&& (!san
|| newline_for_fun
|| *h
!='\n')) {
5345 if (*h
==escapesym
) {
5349 } else if (*h
==controlsym
&& h
[-1]=='\n') {
5352 h
= scan_request(h
);
5353 if (h
&& san
&& h
[-1]=='\n') h
--;
5354 } else if (mandoc_line
5355 && ((*(h
-1)) && (isspace(*(h
-1)) || (*(h
-1))=='\n'))
5356 && *(h
) && isupper(*(h
))
5357 && *(h
+1) && islower(*(h
+1))
5358 && *(h
+2) && isspace(*(h
+2))) {
5359 // mdoc(7) embedded command eg ".It Fl Ar arg1 Fl Ar arg2"
5361 h
= scan_request(h
);
5362 if (san
&& h
[-1]=='\n') h
--;
5363 } else if (*h
==nobreaksym
&& h
[-1]=='\n') {
5366 h
= scan_request(h
);
5367 if (san
&& h
[-1]=='\n') h
--;
5370 if (still_dd
&& isalnum(*h
) && h
[-1]=='\n') {
5371 /* sometimes a .HP request is not followed by a .br request */
5410 if (h
!= c
&& h
[-1]=='\n' && fillout
) {
5415 if (contained_tab
&& fillout
) {
5424 intbuff
[ibp
++]='\n';
5431 /* like a typewriter, not like TeX */
5432 tabstops
[19]=curpos
+1;
5433 while (curtab
<maxtstop
&& tabstops
[curtab
]<=curpos
)
5435 if (curtab
<maxtstop
) {
5437 while (curpos
<tabstops
[curtab
]) {
5439 if (ibp
>480) { FLUSHIBP
; }
5444 while (curpos
<tabstops
[curtab
]) {
5454 if (*h
==' ' && (h
[-1]=='\n' || usenbsp
)) {
5456 if (!usenbsp
&& fillout
) {
5461 if (usenbsp
) out_html(" "); else intbuff
[ibp
++]=' ';
5462 } else if (*h
>31 && *h
<127) intbuff
[ibp
++]=*h
;
5463 else if (((unsigned char)(*h
))>127) {
5469 if (ibp
> (MED_STR_MAX
- 20)) FLUSHIBP
;
5474 if (buffer
) buffer
[buffpos
]='\0';
5475 if (san
&& h
&& *h
) h
++;
5476 newline_for_fun
=exnewline_for_fun
;
5482 scaninbuff
=exscaninbuff
;
5489 static char *scan_troff_mandoc(char *c
, bool san
, char **result
)
5493 bool oldval
= mandoc_line
;
5495 while (*end
&& *end
!= '\n') {
5500 && ispunct(*(end
- 1))
5501 && isspace(*(end
- 2)) && *(end
- 2) != '\n') {
5502 /* Don't format lonely punctuation E.g. in "xyz ," format
5503 * the xyz and then append the comma removing the space.
5506 ret
= scan_troff(c
, san
, result
);
5507 *(end
- 2) = *(end
- 1);
5511 ret
= scan_troff(c
, san
, result
);
5513 mandoc_line
= oldval
;
5518 void scan_man_page(const char *man_page
)
5523 kDebug(7107) << "Start scanning man page";
5526 // Unlike man2html, we actually call this several times, hence the need to
5527 // properly cleanup all those static vars
5528 s_ifelseval
.clear();
5530 s_characterDefinitionMap
.clear();
5531 InitCharacterDefinitions();
5533 s_stringDefinitionMap
.clear();
5534 InitStringDefinitions();
5536 s_numberDefinitionMap
.clear();
5537 InitNumberDefinitions();
5539 s_argumentList
.clear();
5543 s_dollarZero
= ""; // No macro called yet!
5545 output_possible
= false;
5546 int strLength
= qstrlen(man_page
);
5547 char *buf
= new char[strLength
+ 2];
5548 qstrcpy(buf
+1, man_page
);
5551 kDebug(7107) << "Parse man page";
5553 scan_troff(buf
+1,0,NULL
);
5555 kDebug(7107) << "Man page parsed!";
5557 while (itemdepth
|| dl_set
[itemdepth
]) {
5558 out_html("</DL>\n");
5559 if (dl_set
[itemdepth
]) dl_set
[itemdepth
]=0;
5560 else if (itemdepth
> 0) itemdepth
--;
5563 out_html(set_font("R"));
5564 out_html(change_to_size(0));
5572 output_real("</div><div style=\"margin-left: 2cm\">\n");
5576 if (output_possible
) {
5577 // The output is buggy wrt to how divs are handled. Fixing it would
5578 // require closing divs before other block-level elements are output,
5579 // and I do not feel like going to find them all.
5580 output_real("</div></div></div></div>\n");
5582 output_real("<div id=\"footer\"><div id=\"footer_text\">\n");
5583 #ifdef SIMPLE_MAN2HTML
5584 output_real("Generated by kio_man");
5586 output_real("Generated by kio_man, KDE version " KDE_VERSION_STRING
);
5588 output_real("</div></div>\n\n");
5590 output_real("</BODY>\n</HTML>\n");
5595 s_characterDefinitionMap
.clear();
5596 s_stringDefinitionMap
.clear();
5597 s_numberDefinitionMap
.clear();
5598 s_argumentList
.clear();
5600 // reinit static variables for reuse
5614 for (int i
= 0; i
< 20; i
++)
5617 for (int i
= 0; i
< 12; i
++)
5618 tabstops
[i
] = (i
+1)*8;
5622 mandoc_name_count
= 0;
5625 #ifdef SIMPLE_MAN2HTML
5626 void output_real(const char *insert
)
5631 char *read_man_page(const char *filename
)
5634 char *man_buf
= NULL
;
5636 FILE *man_stream
= NULL
;
5639 if (stat(filename
, &stbuf
) == -1) {
5640 std::cerr
<< "read_man_page: can not find " << filename
<< endl
;
5643 if (!S_ISREG(stbuf
.st_mode
)) {
5644 std::cerr
<< "read_man_page: no file " << filename
<< endl
;
5647 buf_size
= stbuf
.st_size
;
5648 man_buf
= stralloc(buf_size
+5);
5650 man_stream
= fopen(filename
, "r");
5653 if (fread(man_buf
+1, 1, buf_size
, man_stream
) == buf_size
) {
5654 man_buf
[buf_size
] = '\n';
5655 man_buf
[buf_size
+ 1] = man_buf
[buf_size
+ 2] = '\0';
5665 #ifndef KIO_MAN_TEST
5666 int main(int argc
, char **argv
)
5670 std::cerr
<< "call: " << argv
[0] << " <filename>\n";
5673 if (chdir(argv
[1])) {
5674 char *buf
= read_man_page(argv
[1]);
5680 DIR *dir
= opendir(".");
5682 while ((ent
= readdir(dir
)) != NULL
) {
5683 cerr
<< "converting " << ent
->d_name
<< endl
;
5684 char *buf
= read_man_page(ent
->d_name
);
5699 // kate: space-indent on; indent-width 4; replace-tabs on;