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 <config-runtime.h>
120 #include <QtCore/QByteArray>
121 #include <QtCore/QDateTime>
122 #include <QtCore/QMap>
123 #include <QtCore/QStack>
124 #include <QtCore/QString>
126 #ifdef SIMPLE_MAN2HTML
130 # include <sys/stat.h>
131 # define kDebug(x) cerr
132 # define kWarning(x) cerr << "WARNING "
133 # define BYTEARRAY(x) x.constData()
135 # include <QTextCodec>
137 # include <kdeversion.h>
138 # define BYTEARRAY(x) x
143 #include "man2html.h"
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
158 #define DOCTYPE "<!DOCTYPE HTML>"
160 #define DOCTYPE "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
163 /* mdoc(7) Bl/El lists to HTML list types */
164 #define BL_DESC_LIST 1
165 #define BL_BULLET_LIST 2
166 #define BL_ENUM_LIST 4
168 /* mdoc(7) Bd/Ed example(?) blocks */
172 static int s_nroff
= 1; // NROFF mode by default
174 static int mandoc_name_count
= 0; /* Don't break on the first Nm */
176 static char *stralloc(int len
)
178 /* allocate enough for len + NULL */
179 char *news
= new char [len
+1];
180 #ifdef SIMPLE_MAN2HTML
183 cerr
<< "man2html: out of memory" << endl
;
187 // modern compilers do not return a NULL pointer for a new
192 static char *strlimitcpy(char *to
, char *from
, int n
, int limit
)
193 { /* Assumes space for limit plus a null */
194 const int len
= n
> limit
? limit
: n
;
195 qstrncpy(to
, from
, len
+ 1);
200 /* below this you should not change anything unless you know a lot
201 ** about this program or about troff.
205 /// Structure for character definitions
213 const char NEWLINE
[2]="\n";
216 * Class for defining strings and macros
218 class StringDefinition
221 StringDefinition( void ) : m_length(0) {}
222 StringDefinition( int len
, const char* cstr
) : m_length( len
), m_output( cstr
) {}
224 int m_length
; ///< Length of output text
225 QByteArray m_output
; ///< Defined string
229 * Class for defining number registers
230 * \note Not for internal read-only registers
232 class NumberDefinition
235 NumberDefinition( void ) : m_value(0), m_increment(0) {}
236 NumberDefinition( int value
) : m_value( value
), m_increment(0) {}
237 NumberDefinition( int value
, int incr
) : m_value( value
), m_increment( incr
) {}
239 int m_value
; ///< value of number register
240 int m_increment
; ///< Increment of number register
241 // ### TODO: display form (.af)
245 * Map of character definitions
247 static QMap
<QByteArray
,StringDefinition
> s_characterDefinitionMap
;
250 * Map of string variable and macro definitions
251 * \note String variables and macros are the same thing!
253 static QMap
<QByteArray
,StringDefinition
> s_stringDefinitionMap
;
256 * Map of number registers
257 * \note Intern number registers (starting with a dot are not handled here)
259 static QMap
<QByteArray
,NumberDefinition
> s_numberDefinitionMap
;
261 static void fill_old_character_definitions( void );
264 * Initialize character variables
266 static void InitCharacterDefinitions( void )
268 fill_old_character_definitions();
269 // ### HACK: as we are converting to HTML too early, define characters with HTML references
270 s_characterDefinitionMap
.insert( "<-", StringDefinition( 1, "←" ) ); // <-
271 s_characterDefinitionMap
.insert( "->", StringDefinition( 1, "→" ) ); // ->
272 s_characterDefinitionMap
.insert( "<>", StringDefinition( 1, "↔" ) ); // <>
273 s_characterDefinitionMap
.insert( "<=", StringDefinition( 1, "≤" ) ); // <=
274 s_characterDefinitionMap
.insert( ">=", StringDefinition( 1, "≥" ) ); // >=
279 * Initialize string variables
281 static void InitStringDefinitions( void )
283 // mdoc-only, see mdoc.samples(7)
284 s_stringDefinitionMap
.insert( "<=", StringDefinition( 1, "≤" ) );
285 s_stringDefinitionMap
.insert( ">=", StringDefinition( 1, "≥" ) );
286 s_stringDefinitionMap
.insert( "Rq", StringDefinition( 1, "”" ) );
287 s_stringDefinitionMap
.insert( "Lq", StringDefinition( 1, "“" ) );
288 s_stringDefinitionMap
.insert( "ua", StringDefinition( 1, "&circ" ) ); // Note this is different from \(ua
289 s_stringDefinitionMap
.insert( "aa", StringDefinition( 1, "´" ) );
290 s_stringDefinitionMap
.insert( "ga", StringDefinition( 1, "`" ) );
291 s_stringDefinitionMap
.insert( "q", StringDefinition( 1, """ ) );
292 s_stringDefinitionMap
.insert( "Pi", StringDefinition( 1, "π" ) );
293 s_stringDefinitionMap
.insert( "Ne", StringDefinition( 1, "≠" ) );
294 s_stringDefinitionMap
.insert( "Le", StringDefinition( 1, "≤" ) );
295 s_stringDefinitionMap
.insert( "Ge", StringDefinition( 1, "≥" ) );
296 s_stringDefinitionMap
.insert( "Lt", StringDefinition( 1, "<" ) );
297 s_stringDefinitionMap
.insert( "Gt", StringDefinition( 1, ">" ) );
298 s_stringDefinitionMap
.insert( "Pm", StringDefinition( 1, "±" ) );
299 s_stringDefinitionMap
.insert( "If", StringDefinition( 1, "∞" ) );
300 s_stringDefinitionMap
.insert( "Na", StringDefinition( 3, "NaN" ) );
301 s_stringDefinitionMap
.insert( "Ba", StringDefinition( 1, "|" ) );
304 s_stringDefinitionMap
.insert( "Tm", StringDefinition( 1, "™" ) ); // \*(TM
305 s_stringDefinitionMap
.insert( "R", StringDefinition( 1, "®" ) ); // \*R
307 // Missing characters from man(7):
308 // \*S "Change to default font size"
309 #ifndef SIMPLE_MAN2HTML
310 // Special KDE KIO man:
311 const QByteArray
kdeversion(KDE_VERSION_STRING
);
312 s_stringDefinitionMap
.insert( ".KDE_VERSION_STRING", StringDefinition( kdeversion
.length(), kdeversion
) );
317 * Initialize number registers
318 * \note Internal read-only registers are not handled here
320 static void InitNumberDefinitions( void )
322 // As the date number registers are more for end-users, better choose local time.
323 // Groff seems to support Gregorian dates only
324 QDate
today( QDate::currentDate() );
325 s_numberDefinitionMap
.insert( "year", today
.year() ); // Y2K-correct year
326 s_numberDefinitionMap
.insert( "yr", today
.year() - 1900 ); // Y2K-incorrect year
327 s_numberDefinitionMap
.insert( "mo", today
.month() );
328 s_numberDefinitionMap
.insert( "dy", today
.day() );
329 s_numberDefinitionMap
.insert( "dw", today
.dayOfWeek() );
333 #define V(A,B) ((A)*256+(B))
335 //used in expand_char, e.g. for "\(bu"
336 // see groff_char(7) for list
337 static CSTRDEF standardchar
[] = {
338 { V('*','*'), 1, "*" },
339 { V('*','A'), 1, "Α" },
340 { V('*','B'), 1, "Β" },
341 { V('*','C'), 1, "Ξ" },
342 { V('*','D'), 1, "Δ" },
343 { V('*','E'), 1, "Ε" },
344 { V('*','F'), 1, "Φ" },
345 { V('*','G'), 1, "Γ" },
346 { V('*','H'), 1, "Θ" },
347 { V('*','I'), 1, "Ι" },
348 { V('*','K'), 1, "Κ" },
349 { V('*','L'), 1, "Λ" },
350 { V('*','M'), 1, "&Mu:" },
351 { V('*','N'), 1, "Ν" },
352 { V('*','O'), 1, "Ο" },
353 { V('*','P'), 1, "Π" },
354 { V('*','Q'), 1, "Ψ" },
355 { V('*','R'), 1, "Ρ" },
356 { V('*','S'), 1, "Σ" },
357 { V('*','T'), 1, "Τ" },
358 { V('*','U'), 1, "Υ" },
359 { V('*','W'), 1, "Ω" },
360 { V('*','X'), 1, "Χ" },
361 { V('*','Y'), 1, "Η" },
362 { V('*','Z'), 1, "Ζ" },
363 { V('*','a'), 1, "α"},
364 { V('*','b'), 1, "β"},
365 { V('*','c'), 1, "ξ"},
366 { V('*','d'), 1, "δ"},
367 { V('*','e'), 1, "ε"},
368 { V('*','f'), 1, "φ"},
369 { V('*','g'), 1, "γ"},
370 { V('*','h'), 1, "θ"},
371 { V('*','i'), 1, "ι"},
372 { V('*','k'), 1, "κ"},
373 { V('*','l'), 1, "λ"},
374 { V('*','m'), 1, "μ" },
375 { V('*','n'), 1, "ν"},
376 { V('*','o'), 1, "ο"},
377 { V('*','p'), 1, "π"},
378 { V('*','q'), 1, "ψ"},
379 { V('*','r'), 1, "ρ"},
380 { V('*','s'), 1, "σ"},
381 { V('*','t'), 1, "τ"},
382 { V('*','u'), 1, "υ"},
383 { V('*','w'), 1, "ω"},
384 { V('*','x'), 1, "χ"},
385 { V('*','y'), 1, "η"},
386 { V('*','z'), 1, "ζ"},
387 { V('+','-'), 1, "±" }, // not in groff_char(7)
388 { V('+','f'), 1, "φ"}, // phi1, we use the standard phi
389 { V('+','h'), 1, "θ"}, // theta1, we use the standard theta
390 { V('+','p'), 1, "ω"}, // omega1, we use the standard omega
391 { V('1','2'), 1, "½" },
392 { V('1','4'), 1, "¼" },
393 { V('3','4'), 1, "¾" },
394 { V('F','i'), 1, "ffi" }, // ffi ligature
395 { V('F','l'), 1, "ffl" }, // ffl ligature
396 { V('a','p'), 1, "~" },
397 { V('b','r'), 1, "|" },
398 { V('b','u'), 1, "•" },
399 { V('b','v'), 1, "|" },
400 { V('c','i'), 1, "○" }, // circle ### TODO verify
401 { V('c','o'), 1, "©" },
402 { V('c','t'), 1, "¢" },
403 { V('d','e'), 1, "°" },
404 { V('d','g'), 1, "†" },
405 { V('d','i'), 1, "÷" },
406 { V('e','m'), 1, "&emdash;" },
407 { V('e','n'), 1, "&endash;"},
408 { V('e','q'), 1, "=" },
409 { V('e','s'), 1, "∅" },
410 { V('f','f'), 1, "�xFB00;" }, // ff ligature
411 { V('f','i'), 1, "�xFB01;" }, // fi ligature
412 { V('f','l'), 1, "�xFB02;" }, // fl ligature
413 { V('f','m'), 1, "′" },
414 { V('g','a'), 1, "`" },
415 { V('h','y'), 1, "-" },
416 { V('l','c'), 2, "|¯" }, // ### TODO: not in groff_char(7)
417 { V('l','f'), 2, "|_" }, // ### TODO: not in groff_char(7)
418 { V('l','k'), 1, "<FONT SIZE=+2>{</FONT>" }, // ### TODO: not in groff_char(7)
419 { V('m','i'), 1, "-" }, // ### TODO: not in groff_char(7)
420 { V('m','u'), 1, "×" },
421 { V('n','o'), 1, "¬" },
422 { V('o','r'), 1, "|" },
423 { V('p','l'), 1, "+" },
424 { V('r','c'), 2, "¯|" }, // ### TODO: not in groff_char(7)
425 { V('r','f'), 2, "_|" }, // ### TODO: not in groff_char(7)
426 { V('r','g'), 1, "®" },
427 { V('r','k'), 1, "<FONT SIZE=+2>}</FONT>" }, // ### TODO: not in groff_char(7)
428 { V('r','n'), 1, "‾" },
429 { V('r','u'), 1, "_" },
430 { V('s','c'), 1, "§" },
431 { V('s','l'), 1, "/" },
432 { V('s','q'), 2, "□" }, // WHITE SQUARE
433 { V('t','s'), 1, "ς" }, // FINAL SIGMA
434 { V('u','l'), 1, "_" },
435 { V('-','D'), 1, "Ð" },
436 { V('S','d'), 1, "ð" },
437 { V('T','P'), 1, "Þ" },
438 { V('T','p'), 1, "þ" },
439 { V('A','E'), 1, "Æ" },
440 { V('a','e'), 1, "æ" },
441 { V('O','E'), 1, "Œ" },
442 { V('o','e'), 1, "œ" },
443 { V('s','s'), 1, "ß" },
444 { V('\'','A'), 1, "Á" },
445 { V('\'','E'), 1, "É" },
446 { V('\'','I'), 1, "Í" },
447 { V('\'','O'), 1, "Ó" },
448 { V('\'','U'), 1, "Ú" },
449 { V('\'','Y'), 1, "Ý" },
450 { V('\'','a'), 1, "á" },
451 { V('\'','e'), 1, "é" },
452 { V('\'','i'), 1, "í" },
453 { V('\'','o'), 1, "ó" },
454 { V('\'','u'), 1, "ú" },
455 { V('\'','y'), 1, "ý" },
456 { V(':','A'), 1, "Ä" },
457 { V(':','E'), 1, "Ë" },
458 { V(':','I'), 1, "Ï" },
459 { V(':','O'), 1, "Ö" },
460 { V(':','U'), 1, "Ü" },
461 { V(':','a'), 1, "ä" },
462 { V(':','e'), 1, "ë" },
463 { V(':','i'), 1, "ï" },
464 { V(':','o'), 1, "ö" },
465 { V(':','u'), 1, "ü" },
466 { V(':','y'), 1, "ÿ" },
467 { V('^','A'), 1, "Â" },
468 { V('^','E'), 1, "Ê" },
469 { V('^','I'), 1, "Î" },
470 { V('^','O'), 1, "Ô" },
471 { V('^','U'), 1, "Û" },
472 { V('^','a'), 1, "â" },
473 { V('^','e'), 1, "ê" },
474 { V('^','i'), 1, "î" },
475 { V('^','o'), 1, "ô" },
476 { V('^','u'), 1, "û" },
477 { V('`','A'), 1, "À" },
478 { V('`','E'), 1, "È" },
479 { V('`','I'), 1, "Ì" },
480 { V('`','O'), 1, "Ò" },
481 { V('`','U'), 1, "Ù" },
482 { V('`','a'), 1, "à" },
483 { V('`','e'), 1, "è" },
484 { V('`','i'), 1, "ì" },
485 { V('`','o'), 1, "ò" },
486 { V('`','u'), 1, "ù" },
487 { V('~','A'), 1, "Ã" },
488 { V('~','N'), 1, "Ñ" },
489 { V('~','O'), 1, "Õ" },
490 { V('~','a'), 1, "ã" },
491 { V('~','n'), 1, "&ntidle;" },
492 { V('~','o'), 1, "&otidle;" },
493 { V(',','C'), 1, "Ç" },
494 { V(',','c'), 1, "ç" },
495 { V('/','L'), 1, "Ł" },
496 { V('/','l'), 1, "ł" },
497 { V('/','O'), 1, "Ø" },
498 { V('/','o'), 1, "ø" },
499 { V('o','A'), 1, "Å" },
500 { V('o','a'), 1, "å" },
501 { V('a','"'), 1, "\"" },
502 { V('a','-'), 1, "¯" },
503 { V('a','.'), 1, "." },
504 { V('a','^'), 1, "ˆ" },
505 { V('a','a'), 1, "´" },
506 { V('a','b'), 1, "`" },
507 { V('a','c'), 1, "¸" },
508 { V('a','d'), 1, "¨" },
509 { V('a','h'), 1, "˂" }, // caron
510 { V('a','o'), 1, "˚" }, // ring
511 { V('a','~'), 1, "˜" },
512 { V('h','o'), 1, "˛" }, // ogonek
513 { V('.','i'), 1, "ı" }, // dot less i
514 { V('C','s'), 1, "¤" }, //krazy:exclude=spelling
515 { V('D','o'), 1, "$" },
516 { V('P','o'), 1, "£" },
517 { V('Y','e'), 1, "¥" },
518 { V('F','n'), 1, "ƒ" },
519 { V('F','o'), 1, "«" },
520 { V('F','c'), 1, "»" },
521 { V('f','o'), 1, "‹" }, // single left guillemet
522 { V('f','c'), 1, "›" }, // single right guillemet
523 { V('r','!'), 1, "&iecl;" },
524 { V('r','?'), 1, "¿" },
525 { V('O','f'), 1, "ª" },
526 { V('O','m'), 1, "º" },
527 { V('p','c'), 1, "·" },
528 { V('S','1'), 1, "¹" },
529 { V('S','2'), 1, "²" },
530 { V('S','3'), 1, "³" },
531 { V('<','-'), 1, "←" },
532 { V('-','>'), 1, "→" },
533 { V('<','>'), 1, "↔" },
534 { V('d','a'), 1, "↓" },
535 { V('u','a'), 1, "↑" },
536 { V('l','A'), 1, "⇐" },
537 { V('r','A'), 1, "⇒" },
538 { V('h','A'), 1, "⇔" },
539 { V('d','A'), 1, "⇓" },
540 { V('u','A'), 1, "⇑" },
541 { V('b','a'), 1, "|" },
542 { V('b','b'), 1, "¦" },
543 { V('t','m'), 1, "™" },
544 { V('d','d'), 1, "‡" },
545 { V('p','s'), 1, "¶" },
546 { V('%','0'), 1, "‰" },
547 { V('f','/'), 1, "⁄" }, // Fraction slash
548 { V('s','d'), 1, "″" },
549 { V('h','a'), 1, "^" },
550 { V('t','i'), 1, "&tidle;" },
551 { V('l','B'), 1, "[" },
552 { V('r','B'), 1, "]" },
553 { V('l','C'), 1, "{" },
554 { V('r','C'), 1, "}" },
555 { V('l','a'), 1, "<" },
556 { V('r','a'), 1, ">" },
557 { V('l','h'), 1, "≤" },
558 { V('r','h'), 1, "≥" },
559 { V('B','q'), 1, "„" },
560 { V('b','q'), 1, "‚" },
561 { V('l','q'), 1, "“" },
562 { V('r','q'), 1, "”" },
563 { V('o','q'), 1, "‘" },
564 { V('c','q'), 1, "’" },
565 { V('a','q'), 1, "'" },
566 { V('d','q'), 1, "\"" },
567 { V('a','t'), 1, "@" },
568 { V('s','h'), 1, "#" },
569 { V('r','s'), 1, "\\" },
570 { V('t','f'), 1, "∴" },
571 { V('~','~'), 1, "≅" },
572 { V('~','='), 1, "≈" },
573 { V('!','='), 1, "≠" },
574 { V('<','='), 1, "≤" },
575 { V('=','='), 1, "≡" },
576 { V('=','~'), 1, "≅" }, // ### TODO: verify
577 { V('>','='), 1, "≥" },
578 { V('A','N'), 1, "∧" },
579 { V('O','R'), 1, "∨" },
580 { V('t','e'), 1, "∃" },
581 { V('f','a'), 1, "∀" },
582 { V('A','h'), 1, "ℵ" },
583 { V('I','m'), 1, "ℑ" },
584 { V('R','e'), 1, "ℜ" },
585 { V('i','f'), 1, "∞" },
586 { V('m','d'), 1, "⋅" },
587 { V('m','o'), 1, "∆" }, // element ### TODO verify
588 { V('n','m'), 1, "∉" },
589 { V('p','t'), 1, "∝" },
590 { V('p','p'), 1, "⊥" },
591 { V('s','b'), 1, "⊂" },
592 { V('s','p'), 1, "⊃" },
593 { V('i','b'), 1, "⊆" },
594 { V('i','p'), 1, "⊇" },
595 { V('i','s'), 1, "∫" },
596 { V('s','r'), 1, "√" },
597 { V('p','d'), 1, "∂" },
598 { V('c','*'), 1, "⊗" },
599 { V('c','+'), 1, "⊕" },
600 { V('c','a'), 1, "∩" },
601 { V('c','u'), 1, "∪" },
602 { V('g','r'), 1, "V" }, // gradient ### TODO Where in Unicode?
603 { V('C','R'), 1, "↵" },
604 { V('s','t'), 2, "-)" }, // "such that" ### TODO Where in Unicode?
605 { V('/','_'), 1, "∠" },
606 { V('w','p'), 1, "℘" },
607 { V('l','z'), 1, "◊" },
608 { V('a','n'), 1, "-" }, // "horizontal arrow extension" ### TODO Where in Unicode?
611 /* default: print code */
614 /* static char eqndelimopen=0, eqndelimclose=0; */
615 static char escapesym
='\\', nobreaksym
='\'', controlsym
='.', fieldsym
=0, padsym
=0;
617 static char *buffer
=NULL
;
618 static int buffpos
=0, buffmax
=0;
619 static bool scaninbuff
=false;
620 static int itemdepth
=0;
621 static int section
=0;
622 static int dl_set
[20]= { 0 };
623 static bool still_dd
=0;
624 static int tabstops
[20] = { 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96 };
625 static int maxtstop
=12;
628 static char *scan_troff(char *c
, bool san
, char **result
);
629 static char *scan_troff_mandoc(char *c
, bool san
, char **result
);
631 static QList
<char*> s_argumentList
;
633 static QByteArray htmlPath
, cssPath
;
635 static QByteArray s_dollarZero
; // Value of $0
637 void setResourcePath(const QByteArray
& _htmlPath
, const QByteArray
& _cssPath
)
643 static void fill_old_character_definitions( void )
645 for (size_t i
= 0; i
< sizeof(standardchar
)/sizeof(CSTRDEF
); i
++)
647 const int nr
= standardchar
[i
].nr
;
648 const char temp
[3] = { nr
/ 256, nr
% 256, 0 };
649 QByteArray
name( temp
);
650 s_characterDefinitionMap
.insert( name
, StringDefinition( standardchar
[i
].slen
, standardchar
[i
].st
) );
654 static char outbuffer
[NULL_TERMINATED(HUGE_STR_MAX
)];
655 static int no_newline_output
=0;
656 static int newline_for_fun
=0;
657 static bool output_possible
=false;
659 static const char *includedirs
[] = {
662 "/usr/local/include",
663 "/opt/local/include",
665 "/usr/X11R6/include",
666 "/usr/openwin/include",
671 static bool ignore_links
=false;
673 static void add_links(char *c
)
676 ** Add the links to the output.
677 ** At the moment the following are recognized:
679 ** name(*) -> ../man?/name.*
680 ** method://string -> method://string
681 ** www.host.name -> http://www.host.name
682 ** ftp.host.name -> ftp://ftp.host.name
683 ** name@host -> mailto:name@host
684 ** <name.h> -> file:/usr/include/name.h (guess)
686 ** Other possible links to add in the future:
688 ** /dir/dir/file -> file:/dir/dir/file
698 const int numtests
=6; // Nmber of tests
699 char *idtest
[numtests
]; // url, mailto, www, ftp, manpage, C header file
701 /* search for (section) */
703 idtest
[0]=strstr(c
+1,"://");
704 idtest
[1]=strchr(c
+1,'@');
705 idtest
[2]=strstr(c
,"www.");
706 idtest
[3]=strstr(c
,"ftp.");
707 idtest
[4]=strchr(c
+1,'(');
708 idtest
[5]=strstr(c
+1,".h>");
709 for (i
=0; i
<numtests
; ++i
) nr
+= (idtest
[i
]!=NULL
);
712 for (i
=0; i
<numtests
; i
++)
713 if (idtest
[i
] && (j
<0 || idtest
[i
]<idtest
[j
])) j
=i
;
715 case 5: { /* <name.h> */
719 while (g
>c
&& g
[-1]!=';') g
--;
720 bool wrote_include
= false;
725 QByteArray
file(g
, h
- g
+ 1);
726 file
= file
.trimmed();
727 for (int index
= 0; includedirs
[index
]; index
++) {
728 QByteArray
str( includedirs
[index
] );
731 if (!access(str
.data(), R_OK
)) {
732 dir
= includedirs
[index
];
736 if (!dir
.isEmpty()) {
745 str
.append( "<A HREF=\"file:" );
746 str
.append( dir
.data() );
748 str
.append( file
.data() );
750 str
.append( file
.data() );
751 str
.append( "</A>>" );
753 output_real(str
.data());
755 wrote_include
= true;
760 if (!wrote_include
) {
768 case 4: /* manpage */
772 // The character before f must alphanumeric, the end of a HTML tag or the end of a
773 if (g
!=NULL
&& f
>c
&& (g
-f
)<12 && (isalnum(f
[-1]) || f
[-1]=='>' || ( f
[-1] == ';' ) ) &&
774 isdigit(f
[1]) && f
[1]!='0' && ((g
-f
)<=2 || isalpha(f
[2])))
794 kDebug(7107) << "BEFORE SECTION:" << *h
;
795 if ( ( h
> c
+ 5 ) && ( ! memcmp( h
-5, " ", 6 ) ) )
798 kDebug(7107) << "Skip ";
800 else if ( *h
== ';' )
802 // Not a non-breaking space, so probably not ok
809 /* this might be a link */
810 /* skip html makeup */
811 while (h
>c
&& *h
=='>') {
812 while (h
!=c
&& *h
!='<') h
--;
820 const int index
= fstr
.indexOf(')', 2);
823 subsec
= fstr
.mid(2, index
- 2);
824 else // No closing ')' found, take first character as subsection.
825 subsec
= fstr
.mid(2, 1);
826 while (h
>c
&& (isalnum(h
[-1]) || h
[-1]=='_'
827 || h
[-1]==':' || h
[-1]=='-' || h
[-1]=='.'))
835 QByteArray
str("<a href=\"man:");
839 if ( !subsec
.isEmpty() )
840 str
+= subsec
.toLower();
844 output_real(str
.data());
858 while (*g
&& (isalnum(*g
) || *g
=='_' || *g
=='-' || *g
=='+' ||
859 *g
=='.' || *g
=='/')) g
++;
867 str
.append( "<A HREF=\"" );
868 str
.append( j
== 3 ? "ftp" : "http" );
873 str
.append( "</A>" );
874 output_real(str
.data());
886 while (g
>c
&& (isalnum(g
[-1]) || g
[-1]=='_' || g
[-1]=='-' ||
887 g
[-1]=='+' || g
[-1]=='.' || g
[-1]=='%')) g
--;
888 if (g
-7>=c
&& g
[-1]==':')
890 // We have perhaps an email address starting with mailto:
891 if (!qstrncmp("mailto:",g
-7,7))
895 while (*h
&& (isalnum(*h
) || *h
=='_' || *h
=='-' || *h
=='+' ||
898 if (h
-f
>4 && f
-g
>1) {
905 str
.append( "<A HREF=\"mailto:" );
909 str
.append( "</A>" );
910 output_real(str
.data());
923 while (g
>c
&& isalpha(g
[-1]) && islower(g
[-1])) g
--;
925 while (*h
&& !isspace(*h
) && *h
!='<' && *h
!='>' && *h
!='"' &&
927 if (f
-g
>2 && f
-g
<7 && h
-f
>3) {
934 str
.append( "<A HREF=\"" );
938 str
.append( "</A>" );
939 output_real(str
.data());
953 if (idtest
[0] && idtest
[0]<=c
) idtest
[0]=strstr(c
+1,"://");
954 if (idtest
[1] && idtest
[1]<=c
) idtest
[1]=strchr(c
+1,'@');
955 if (idtest
[2] && idtest
[2]<c
) idtest
[2]=strstr(c
,"www.");
956 if (idtest
[3] && idtest
[3]<c
) idtest
[3]=strstr(c
,"ftp.");
957 if (idtest
[4] && idtest
[4]<=c
) idtest
[4]=strchr(c
+1,'(');
958 if (idtest
[5] && idtest
[5]<=c
) idtest
[5]=strstr(c
+1,".h>");
959 for (i
=0; i
<numtests
; i
++) nr
+= (idtest
[i
]!=NULL
);
964 static QByteArray current_font
;
965 static int current_size
=0;
966 static int fillout
=1;
968 static void out_html(const char *c
)
972 // Added, probably due to the const?
973 char *c2
= qstrdup(c
);
978 if (no_newline_output
) {
982 if (!no_newline_output
) c2
[i
-1]=c2
[i
];
983 if (c2
[i
]=='\n') no_newline_output
=0;
986 if (!no_newline_output
) c2
[i
-1]=0;
990 if (buffpos
>=buffmax
) {
991 char *h
= new char[buffmax
*2];
993 #ifdef SIMPLE_MAN2HTML
996 cerr
<< "Memory full, cannot output!" << endl
;
1000 // modern compiler do not return a NULL for a new
1002 memcpy(h
, buffer
, buffmax
);
1007 buffer
[buffpos
++]=*c2
++;
1010 if (output_possible
) {
1012 outbuffer
[obp
++]=*c2
;
1013 if (*c
=='\n' || obp
>= HUGE_STR_MAX
) {
1014 outbuffer
[obp
]='\0';
1015 add_links(outbuffer
);
1024 static QByteArray
set_font( const QByteArray
& name
)
1026 // Every font but R (Regular) creates <span> elements
1028 if ( current_font
!= "R" && !current_font
.isEmpty() )
1029 markup
+= "</span>";
1030 const uint len
= name
.length();
1034 const char lead
= name
[0];
1037 case 'P': // ### TODO: this seems to mean "precedent font"
1038 case 'R': break; // regular, do nothing
1039 case 'I': markup
+= "<span style=\"font-style:italic\">"; break;
1040 case 'B': markup
+= "<span style=\"font-weight:bold\">"; break;
1041 case 'L': markup
+= "<span style=\"font-family:monospace\">"; break; // ### What's L?
1042 default: fontok
= false;
1045 else if ( len
== 2 )
1048 markup
+= "<span style=\"font-style:italic;font-weight:bold\">";
1050 else if ( name
== "CR" )
1051 markup
+= "<span style=\"font-family:monospace\">";
1052 else if ( name
== "CW" ) // CW is used by pod2man(1) (part of perldoc(1))
1053 markup
+= "<span style=\"font-family:monospace\">";
1054 else if ( name
== "CI" )
1055 markup
+= "<span style=\"font-family:monospace;font-style:italic\">";
1056 else if ( name
== "CB" )
1057 markup
+= "<span style=\"font-family:monospace;font-weight:bold\">";
1059 else if ( name
== "TR" )
1060 markup
+= "<span style=\"font-family:serif\">";
1061 else if ( name
== "TI" )
1062 markup
+= "<span style=\"font-family:serif;font-style:italic\">";
1063 else if ( name
== "TB" )
1064 markup
+= "<span style=\"font-family:serif;font-weight:bold\">";
1066 else if ( name
== "HR" )
1067 markup
+= "<span style=\"font-family:sans-serif\">";
1068 else if ( name
== "HI" )
1069 markup
+= "<span style=\"font-family:sans-serif;font-style:italic\">";
1070 else if ( name
== "HB" )
1071 markup
+= "<span style=\"font-family:sans-serif;font-weight:bold\">";
1075 else if ( len
== 3 )
1077 if ( name
== "CBI" )
1078 markup
+= "<span style=\"font-family:monospace;font-style:italic;font-weight:bold\">";
1079 else if ( name
== "TBI" )
1080 markup
+= "<span style=\"font-family:serif;font-style:italic;font-weight:bold\">";
1081 else if ( name
== "HBI" )
1082 markup
+= "<span style=\"font-family:sans-serif;font-style:italic;font-weight:bold\">";
1085 current_font
= name
;
1087 current_font
= "R"; // Still nothing, then it is 'R' (Regular)
1091 static QByteArray
change_to_size(int nr
)
1095 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1096 case '7': case '8': case '9': nr
=nr
-'0'; break;
1098 default: nr
=current_size
+nr
; if (nr
>9) nr
=9; if (nr
< -9) nr
=-9; break;
1100 if ( nr
== current_size
)
1102 const QByteArray
font ( current_font
);
1104 markup
= set_font("R");
1106 markup
+= "</FONT>";
1110 markup
+= "<FONT SIZE=\"";
1118 markup
+= char( nr
+ '0' );
1121 markup
+= set_font( font
);
1125 /* static int asint=0; */
1126 static int intresult
=0;
1128 #define SKIPEOL while (*c && *c++!='\n') {}
1130 static bool skip_escape
=false;
1131 static bool single_escape
=false;
1133 static char *scan_escape_direct( char *c
, QByteArray
& cstr
);
1136 * scan a named character
1139 static QByteArray
scan_named_character( char*& c
)
1144 // \*(ab Name of two characters
1145 if ( c
[1] == escapesym
)
1148 c
= scan_escape_direct( c
+2, cstr
);
1149 // ### 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.
1159 else if ( *c
== '[' )
1161 // \*[long_name] Long name
1162 // Named character groff(7)
1163 // We must find the ] to get a name
1165 while ( *c
&& *c
!= ']' && *c
!= '\n' )
1167 if ( *c
== escapesym
)
1170 c
= scan_escape_direct( c
+1, cstr
);
1171 const int result
= cstr
.indexOf(']');
1176 // Note: we drop the characters after the ]
1177 name
+= cstr
.left( result
);
1186 if ( !*c
|| *c
== '\n' )
1188 kDebug(7107) << "Found linefeed! Could not parse character name: " << BYTEARRAY( name
);
1193 else if ( *c
=='C' || c
[1]== '\'' )
1197 while ( *c
&& *c
!= '\'' && *c
!= '\n' )
1199 if ( *c
== escapesym
)
1202 c
= scan_escape_direct( c
+1, cstr
);
1203 const int result
= cstr
.indexOf('\'');
1208 // Note: we drop the characters after the ]
1209 name
+= cstr
.left( result
);
1218 if ( !*c
|| *c
== '\n' )
1220 kDebug(7107) << "Found linefeed! Could not parse (\\C mode) character name: " << BYTEARRAY( name
);
1225 // Note: characters with a one character length name doe not exist, as they would collide with other escapes
1227 // Now we have the name, let us find it between the string names
1228 QMap
<QByteArray
,StringDefinition
>::const_iterator it
=s_characterDefinitionMap
.find(name
);
1229 if (it
==s_characterDefinitionMap
.end())
1231 kDebug(7107) << "EXCEPTION: cannot find character with name: " << BYTEARRAY( name
);
1232 // No output, as an undefined string is empty by default
1237 kDebug(7107) << "Character with name: \"" << BYTEARRAY( name
) << "\" => " << BYTEARRAY( (*it
).m_output
);
1238 return (*it
).m_output
;
1242 static QByteArray
scan_named_string(char*& c
)
1247 // \*(ab Name of two characters
1248 if ( c
[1] == escapesym
)
1251 c
= scan_escape_direct( c
+2, cstr
);
1252 kDebug(7107) << "\\(" << BYTEARRAY( cstr
);
1253 // ### 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.
1263 else if ( *c
== '[' )
1265 // \*[long_name] Long name
1266 // Named character groff(7)
1267 // We must find the ] to get a name
1269 while ( *c
&& *c
!= ']' && *c
!= '\n' )
1271 if ( *c
== escapesym
)
1274 c
= scan_escape_direct( c
+1, cstr
);
1275 const int result
= cstr
.indexOf(']');
1280 // Note: we drop the characters after the ]
1281 name
+= cstr
.left( result
);
1290 if ( !*c
|| *c
== '\n' )
1292 kDebug(7107) << "Found linefeed! Could not parse string name: " << BYTEARRAY( name
);
1299 // \*a Name of one character
1303 // Now we have the name, let us find it between the string names
1304 QMap
<QByteArray
,StringDefinition
>::const_iterator it
=s_stringDefinitionMap
.find(name
);
1305 if (it
==s_stringDefinitionMap
.end())
1307 kDebug(7107) << "EXCEPTION: cannot find string with name: " << BYTEARRAY( name
);
1308 // No output, as an undefined string is empty by default
1313 kDebug(7107) << "String with name: \"" << BYTEARRAY( name
) << "\" => " << BYTEARRAY( (*it
).m_output
);
1314 return (*it
).m_output
;
1318 static QByteArray
scan_dollar_parameter(char*& c
)
1320 int argno
= 0; // No dollar argument number yet!
1323 //kDebug(7107) << "$0";
1325 return s_dollarZero
;
1327 else if ( *c
>= '1' && *c
<= '9' )
1329 //kDebug(7107) << "$ direct";
1330 argno
= ( *c
- '0' );
1333 else if ( *c
== '(' )
1335 //kDebug(7107) << "$(";
1336 if ( c
[1] && c
[2] && c
[1] >= '0' && c
[1] <= '9' && c
[2] >= '0' && c
[2] <= '9' )
1338 argno
= ( c
[1] - '0' ) * 10 + ( c
[2] - '0' );
1352 else if ( *c
== '[' )
1354 //kDebug(7107) << "$[";
1357 while ( *c
&& *c
>='0' && *c
<='9' && *c
!=']' )
1360 argno
+= ( *c
- '0' );
1369 else if ( ( *c
== '*' ) || ( *c
== '@' ) )
1371 const bool quote
= ( *c
== '@' );
1372 QList
<char*>::const_iterator it
= s_argumentList
.begin();
1375 for ( ; it
!= s_argumentList
.end(); ++it
)
1380 param
+= '\"'; // Not as HTML, as it could be used by macros !
1383 param
+= '\"'; // Not as HTML, as it could be used by macros!
1391 kDebug(7107) << "EXCEPTION: unknown parameter $" << *c
;
1394 //kDebug(7107) << "ARG $" << argno;
1395 if ( !s_argumentList
.isEmpty() && argno
> 0 )
1397 //kDebug(7107) << "ARG $" << argno << " OK!";
1399 if ( argno
>= s_argumentList
.size() )
1401 kDebug(7107) << "EXCEPTION: cannot find parameter $" << (argno
+1);
1405 return s_argumentList
[argno
];
1410 /// return the value of read-only number registers
1411 static int read_only_number_register( const QByteArray
& name
)
1413 // Internal read-only variables
1416 kDebug(7107) << "\\n[.$] == " << s_argumentList
.size();
1417 return s_argumentList
.size();
1419 else if ( name
== ".g" )
1420 return 0; // We are not groff(1)
1421 else if ( name
== ".s" )
1422 return current_size
;
1424 // ### TODO: map the fonts to a number
1425 else if ( name
== ".f" )
1426 return current_font
;
1428 else if ( name
== ".P" )
1429 return 0; // We are not printing
1430 else if ( name
== ".A" )
1432 #ifndef SIMPLE_MAN2HTML
1433 // Special KDE KIO man:
1434 else if ( name
== ".KDE_VERSION_MAJOR" )
1435 return KDE_VERSION_MAJOR
;
1436 else if ( name
== ".KDE_VERSION_MINOR" )
1437 return KDE_VERSION_MINOR
;
1438 else if ( name
== ".KDE_VERSION_RELEASE" )
1439 return KDE_VERSION_RELEASE
;
1440 else if ( name
== ".KDE_VERSION" )
1443 // ### TODO: should .T be set to "html"? But we are not the HTML post-processor. :-(
1445 // ### TODO: groff defines many more read-only number registers
1446 kDebug(7107) << "EXCEPTION: unknown read-only number register: " << BYTEARRAY( name
);
1448 return 0; // Undefined variable
1452 /// get the value of a number register and auto-increment if asked
1453 static int scan_number_register( char*& c
)
1455 int sign
= 0; // Sign for auto-increment (if any)
1458 case '+': sign
= 1; c
++; break;
1459 case '-': sign
= -1; c
++; break;
1471 else if ( *c
== '-' )
1476 while ( *c
&& *c
!= ']' && *c
!= '\n' )
1478 // ### TODO: a \*[string] could be inside and should be processed
1482 if ( !*c
|| *c
== '\n' )
1484 kDebug(7107) << "Found linefeed! Could not parse number register name: " << BYTEARRAY( name
);
1489 else if ( *c
== '(' )
1497 else if ( *c
== '-' )
1511 if ( name
[0] == '.' )
1513 return read_only_number_register( name
);
1517 QMap
< QByteArray
, NumberDefinition
>::iterator it
= s_numberDefinitionMap
.find( name
);
1518 if ( it
== s_numberDefinitionMap
.end() )
1520 return 0; // Undefined variable
1524 (*it
).m_value
+= sign
* (*it
).m_increment
;
1525 return (*it
).m_value
;
1530 /// get and set font
1531 static QByteArray
scan_named_font( char*& c
)
1536 // \f(ab Name of two characters
1537 if ( c
[1] == escapesym
)
1540 c
= scan_escape_direct( c
+2, cstr
);
1541 kDebug(7107) << "\\(" << BYTEARRAY( cstr
);
1542 // ### 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.
1552 else if ( *c
== '[' )
1554 // \f[long_name] Long name
1555 // We must find the ] to get a name
1557 while ( *c
&& *c
!= ']' && *c
!= '\n' )
1559 if ( *c
== escapesym
)
1562 c
= scan_escape_direct( c
+1, cstr
);
1563 const int result
= cstr
.indexOf(']');
1568 // Note: we drop the characters after the ]
1569 name
+= cstr
.left( result
);
1578 if ( !*c
|| *c
== '\n' )
1580 kDebug(7107) << "Found linefeed! Could not parse font name: " << BYTEARRAY( name
);
1587 // \fa Font name with one character or one digit
1588 // ### HACK do *not* use: name = *c; or name would be empty
1592 //kDebug(7107) << "FONT NAME: " << BYTEARRAY( name );
1593 // Now we have the name, let us find the font
1595 const unsigned int number
= name
.toUInt( &ok
);
1600 const char* fonts
[] = { "R", "I", "B", "BI", "CR" }; // Regular, Italic, Bold, Bold Italic, Courier regular
1601 name
= fonts
[ number
];
1605 kDebug(7107) << "EXCEPTION: font has too big number: " << BYTEARRAY( name
) << " => " << number
;
1606 name
= "R"; // Let assume Regular
1609 else if ( name
.isEmpty() )
1611 kDebug(7107) << "EXCEPTION: font has no name: " << BYTEARRAY( name
);
1612 name
= "R"; // Let assume Regular
1615 return set_font( name
);
1620 static QByteArray
scan_number_code( char*& c
)
1625 while ( *c
&& ( *c
!= '\n' ) && ( *c
!= '\'' ) )
1631 unsigned int result
= number
.toUInt( &ok
);
1632 if ( ( result
< ' ' ) || ( result
> 65535 ) )
1634 else if ( result
== '\t' )
1640 number
.setNum( result
);
1641 number
.prepend( "&#" );
1642 number
.append( ";" );
1647 // ### TODO known missing escapes from groff(7):
1648 // ### TODO \& \! \) \: \R
1650 static char *scan_escape_direct( char *c
, QByteArray
& cstr
)
1655 bool cplusplus
= true; // Should the c++ call be executed at the end of the function
1660 case 'e': cstr
= "\\"; curpos
++;break; // ### FIXME: it should be the current escape symbol
1661 case '0': // ### TODO Where in Unicode? (space of digit width)
1662 case '~': // non-breakable-space (resizeable!)
1664 case '|': // half-non-breakable-space
1665 case '^': // quarter-non-breakable-space
1666 cstr
= " "; curpos
++; break;
1667 case '"': SKIPEOL
; c
--; break;
1668 // ### TODO \# like \" but does not ignore the end of line (groff(7))
1672 cstr
= scan_dollar_parameter( c
);
1681 c
=scan_escape_direct( c
+1, cstr
);
1685 cstr
= QByteArray( c
, 1 );
1688 case 'k': c
++; if (*c
=='(') c
+=2; // ### FIXME \k[REG] exists too
1702 // Do not go forward as scan_named_character needs the leading symbol
1703 cstr
= scan_named_character( c
);
1710 cstr
= scan_named_string( c
);
1717 cstr
= scan_named_font( c
);
1721 case 's': // ### FIXME: many forms are missing
1724 if (*c
=='-') {j
= -1; c
++;} else if (*c
=='+') {j
=1; c
++;}
1725 if (*c
=='0') c
++; else if (*c
=='\\') {
1727 c
=scan_escape_direct( c
, cstr
);
1728 i
=intresult
; if (!j
) j
=1;
1730 while (isdigit(*c
) && (!i
|| (!j
&& i
<4))) i
=i
*10+(*c
++)-'0';
1731 if (!j
) { j
=1; if (i
) i
=i
-10; }
1732 if (!skip_escape
) cstr
=change_to_size(i
*j
);
1738 intresult
= scan_number_register( c
);
1746 exoutputp
=output_possible
;
1747 exskipescape
=skip_escape
;
1748 output_possible
=false;
1754 if ( *c
== escapesym
)
1755 c
= scan_escape_direct( c
+1, cstr
);
1759 output_possible
=exoutputp
;
1760 skip_escape
=exskipescape
;
1763 case 'l': cstr
= "<HR>"; curpos
=0;
1773 exoutputp
=output_possible
;
1774 exskipescape
=skip_escape
;
1778 if (*c
==escapesym
) c
=scan_escape_direct( c
+1, cstr
);
1780 output_possible
=exoutputp
;
1781 skip_escape
=exskipescape
;
1783 case 'c': no_newline_output
=1; break;
1784 case '{': newline_for_fun
++; break; // Start conditional block
1785 case '}': if (newline_for_fun
) newline_for_fun
--; break; // End conditional block
1786 case 'p': cstr
= "<BR>\n";curpos
=0; break;
1787 case 't': cstr
= "\t";curpos
=(curpos
+8)&0xfff8; break;
1788 case '<': cstr
= "<";curpos
++; break;
1789 case '>': cstr
= ">";curpos
++; break;
1801 cstr
= scan_number_code( c
);
1805 case '\'': cstr
= "´";curpos
++; break; // groff(7) ### TODO verify
1806 case '`': cstr
= "`";curpos
++; break; // groff(7)
1807 case '-': cstr
= "-";curpos
++; break; // groff(7)
1808 case '.': cstr
= ".";curpos
++; break; // groff(7)
1809 default: cstr
= QByteArray( c
, 1 ); curpos
++; break;
1816 static char *scan_escape(char *c
)
1819 char* result
= scan_escape_direct( c
, cstr
);
1829 TABLEITEM(TABLEROW
*row
);
1833 void setContents(const char *_contents
) {
1835 contents
= qstrdup(_contents
);
1837 const char *getContents() const { return contents
; }
1854 void copyLayout(const TABLEITEM
*orig
) {
1856 align
= orig
->align
;
1857 valign
= orig
->valign
;
1858 colspan
= orig
->colspan
;
1859 rowspan
= orig
->rowspan
;
1861 vleft
= orig
->vleft
;
1862 vright
= orig
->vright
;
1863 space
= orig
->space
;
1864 width
= orig
->width
;
1868 int size
,align
,valign
,colspan
,rowspan
,font
,vleft
,vright
,space
,width
;
1888 int length() const { return items
.count(); }
1889 bool has(int index
) {
1890 return (index
>= 0) && (index
< (int)items
.count());
1892 TABLEITEM
&at(int index
) {
1893 return *items
.at(index
);
1896 TABLEROW
*copyLayout() const;
1898 void addItem(TABLEITEM
*item
) {
1901 TABLEROW
*prev
, *next
;
1904 QList
<TABLEITEM
*> items
;
1907 TABLEITEM::TABLEITEM(TABLEROW
*row
) : contents(0), _parent(row
) {
1909 _parent
->addItem(this);
1912 TABLEROW
*TABLEROW::copyLayout() const {
1913 TABLEROW
*newrow
= new TABLEROW();
1915 QListIterator
<TABLEITEM
*> it(items
);
1916 while (it
.hasNext()){
1917 TABLEITEM
*newitem
= new TABLEITEM(newrow
);
1918 newitem
->copyLayout(it
.next());
1923 static const char *tableopt
[]= { "center", "expand", "box", "allbox",
1924 "doublebox", "tab", "linesize",
1926 static int tableoptl
[] = { 6,6,3,6,9,3,8,5,0};
1929 static void clear_table(TABLEROW
*table
)
1934 while (tr1
->prev
) tr1
=tr1
->prev
;
1942 static char *scan_expression(char *c
, int *result
);
1944 static char *scan_format(char *c
, TABLEROW
**result
, int *maxcol
)
1946 TABLEROW
*layout
, *currow
;
1947 TABLEITEM
*curfield
;
1950 clear_table(*result
);
1952 layout
= currow
=new TABLEROW();
1953 curfield
=new TABLEITEM(currow
);
1954 while (*c
&& *c
!='.') {
1956 case 'C': case 'c': case 'N': case 'n':
1957 case 'R': case 'r': case 'A': case 'a':
1958 case 'L': case 'l': case 'S': case 's':
1960 if (curfield
->align
)
1961 curfield
=new TABLEITEM(currow
);
1962 curfield
->align
=toupper(*c
);
1965 case 'i': case 'I': case 'B': case 'b':
1966 curfield
->font
= toupper(*c
);
1971 curfield
->font
= toupper(*c
);
1973 if (!isspace(*c
) && *c
!='.') c
++;
1975 case 't': case 'T': curfield
->valign
='t'; c
++; break;
1979 if (*c
=='+') { j
=1; c
++; }
1980 if (*c
=='-') { j
=-1; c
++; }
1981 while (isdigit(*c
)) i
=i
*10+(*c
++)-'0';
1982 if (j
) curfield
->size
= i
*j
; else curfield
->size
=j
-10;
1986 c
=scan_expression(c
+2,&curfield
->width
);
1989 if (curfield
->align
) curfield
->vleft
++;
1990 else curfield
->vright
++;
1996 case '0': case '1': case '2': case '3': case '4':
1997 case '5': case '6': case '7': case '8': case '9':
1999 while (isdigit(*c
)) i
=i
*10+(*c
++)-'0';
2002 case ',': case '\n':
2003 currow
->next
=new TABLEROW();
2004 currow
->next
->prev
=currow
;
2005 currow
=currow
->next
;
2007 curfield
=new TABLEITEM(currow
);
2015 if (*c
=='.') while (*c
++!='\n');
2020 if (i
>*maxcol
) *maxcol
=i
;
2021 currow
=currow
->next
;
2027 static TABLEROW
*next_row(TABLEROW
*tr
)
2032 return next_row(tr
);
2035 tr
->next
= tr
->copyLayout();
2036 tr
->next
->prev
= tr
;
2041 static char itemreset
[20]="\\fR\\s0";
2043 #define FORWARDCUR do { curfield++; } while (currow->has(curfield) && currow->at(curfield).align=='S');
2045 static char *scan_table(char *c
)
2049 int center
=0, expand
=0, box
=0, border
=0, linesize
=1;
2050 int i
,j
,maxcol
=0, finished
=0;
2052 int oldsize
,oldfillout
;
2054 TABLEROW
*layout
=NULL
, *currow
;
2058 if (*h
=='.') return c
-1;
2059 oldfont
=current_font
;
2060 oldsize
=current_size
;
2062 out_html(set_font("R"));
2063 out_html(change_to_size(0));
2068 while (*h
&& *h
!='\n') h
++;
2070 /* scan table options */
2072 while (isspace(*c
)) c
++;
2073 for (i
=0; tableopt
[i
] && qstrncmp(tableopt
[i
],c
,tableoptl
[i
]);i
++);
2076 case 0: center
=1; break;
2077 case 1: expand
=1; break;
2078 case 2: box
=1; break;
2079 case 3: border
=1; break;
2080 case 4: box
=2; break;
2081 case 5: while (*c
++!='('); itemsep
=*c
++; break;
2082 case 6: while (*c
++!='('); linesize
=0;
2083 while (isdigit(*c
)) linesize
=linesize
*10+(*c
++)-'0';
2085 case 7: while (*c
!=')') c
++;
2093 c
=scan_format(c
,&layout
, &maxcol
);
2095 currow
=next_row(layout
);
2098 while (!finished
&& *c
) {
2101 if ((*c
=='_' || *c
=='=') && (c
[1]==itemsep
|| c
[1]=='\n')) {
2102 if (c
[-1]=='\n' && c
[1]=='\n') {
2104 currow
->prev
->next
=new TABLEROW();
2105 currow
->prev
->next
->next
=currow
;
2106 currow
->prev
->next
->prev
=currow
->prev
;
2107 currow
->prev
=currow
->prev
->next
;
2109 currow
->prev
=layout
=new TABLEROW();
2110 currow
->prev
->prev
=NULL
;
2111 currow
->prev
->next
=currow
;
2113 TABLEITEM
*newitem
= new TABLEITEM(currow
->prev
);
2115 newitem
->colspan
=maxcol
;
2119 if (currow
->has(curfield
)) {
2120 currow
->at(curfield
).align
=*c
;
2124 currow
=next_row(currow
);
2129 } else if (*c
=='T' && c
[1]=='{') {
2136 scan_troff(itemreset
, 0, &g
);
2139 if (currow
->has(curfield
)) {
2140 currow
->at(curfield
).setContents(g
);
2146 currow
=next_row(currow
);
2149 } else if (*c
=='.' && c
[1]=='T' && c
[2]=='&' && c
[-1]=='\n') {
2153 currow
=currow
->prev
;
2155 c
=scan_format(c
,&hr
, &i
);
2161 } else if (*c
=='.' && c
[1]=='T' && c
[2]=='E' && c
[-1]=='\n') {
2165 currow
->prev
->next
=NULL
;
2167 clear_table(currow
);
2169 } else if (*c
=='.' && c
[-1]=='\n' && !isdigit(c
[1])) {
2170 /* skip troff request inside table (usually only .sp ) */
2174 while (*c
&& (*c
!=itemsep
|| c
[-1]=='\\') &&
2175 (*c
!='\n' || c
[-1]=='\\')) c
++;
2177 if (*c
==itemsep
) {i
=1; *c
='\n'; }
2178 if (h
[0]=='\\' && h
[2]=='\n' &&
2179 (h
[1]=='_' || h
[1]=='^')) {
2180 if (currow
->has(curfield
)) {
2181 currow
->at(curfield
).align
=h
[1];
2187 h
=scan_troff(h
,1,&g
);
2188 scan_troff(itemreset
,0, &g
);
2189 if (currow
->has(curfield
)) {
2190 currow
->at(curfield
).setContents(g
);
2198 currow
=next_row(currow
);
2203 /* calculate colspan and rowspan */
2205 while (currow
->next
) currow
=currow
->next
;
2207 int ti
= 0, ti1
= 0, ti2
= -1;
2208 TABLEROW
*prev
= currow
->prev
;
2212 while (prev
->has(ti1
)) {
2213 if (currow
->has(ti
))
2214 switch (currow
->at(ti
).align
) {
2216 if (currow
->has(ti2
)) {
2217 currow
->at(ti2
).colspan
++;
2218 if (currow
->at(ti2
).rowspan
<prev
->at(ti1
).rowspan
)
2219 currow
->at(ti2
).rowspan
=prev
->at(ti1
).rowspan
;
2223 if (prev
->has(ti1
)) prev
->at(ti1
).rowspan
++;
2225 if (ti2
< 0) ti2
=ti
;
2229 } while (currow
->has(ti2
) && currow
->at(ti2
).align
=='S');
2234 if (ti1
>= 0) ti1
++;
2236 currow
=currow
->prev
;
2238 /* produce html output */
2239 if (center
) out_html("<CENTER>");
2240 if (box
==2) out_html("<TABLE BORDER><TR><TD>");
2242 if (box
|| border
) {
2243 out_html(" BORDER");
2244 if (!border
) out_html("><TR><TD><TABLE");
2245 if (expand
) out_html(" WIDTH=\"100%\"");
2251 out_html("<TR VALIGN=top>");
2253 while (currow
->has(curfield
)) {
2254 if (currow
->at(curfield
).align
!='S' && currow
->at(curfield
).align
!='^') {
2256 switch (currow
->at(curfield
).align
) {
2258 currow
->at(curfield
).space
+=4;
2260 out_html(" ALIGN=right");
2263 out_html(" ALIGN=center");
2267 if (!currow
->at(curfield
).valign
&& currow
->at(curfield
).rowspan
>1)
2268 out_html(" VALIGN=center");
2269 if (currow
->at(curfield
).colspan
>1) {
2271 out_html(" COLSPAN=");
2272 sprintf(buf
, "%i", currow
->at(curfield
).colspan
);
2275 if (currow
->at(curfield
).rowspan
>1) {
2277 out_html(" ROWSPAN=");
2278 sprintf(buf
, "%i", currow
->at(curfield
).rowspan
);
2281 j
=j
+currow
->at(curfield
).colspan
;
2283 if (currow
->at(curfield
).size
) out_html(change_to_size(currow
->at(curfield
).size
));
2284 if (currow
->at(curfield
).font
)
2285 out_html(set_font(QByteArray::number(currow
->at(curfield
).font
) ));
2286 switch (currow
->at(curfield
).align
) {
2287 case '=': out_html("<HR><HR>"); break;
2288 case '_': out_html("<HR>"); break;
2290 out_html(currow
->at(curfield
).getContents());
2293 if (currow
->at(curfield
).space
)
2294 for (i
=0; i
<currow
->at(curfield
).space
;i
++) out_html(" ");
2295 if (currow
->at(curfield
).font
) out_html(set_font("R"));
2296 if (currow
->at(curfield
).size
) out_html(change_to_size(0));
2297 if (j
>=maxcol
&& currow
->at(curfield
).align
>'@' && currow
->at(curfield
).align
!='_')
2303 out_html("</TR>\n");
2304 currow
=currow
->next
;
2307 clear_table(layout
);
2309 if (box
&& !border
) out_html("</TABLE>");
2310 out_html("</TABLE>");
2311 if (box
==2) out_html("</TABLE>");
2312 if (center
) out_html("</CENTER>\n");
2313 else out_html("\n");
2314 if (!oldfillout
) out_html("<PRE>");
2316 out_html(change_to_size(oldsize
));
2317 out_html(set_font(oldfont
));
2321 static char *scan_expression( char *c
, int *result
, const unsigned int numLoop
)
2323 int value
=0,value2
,sign
=1,opex
=0;
2327 c
=scan_expression(c
+1, &value
);
2329 } else if (*c
=='n') {
2332 } else if (*c
=='t') {
2335 } else if (*c
=='\'' || *c
=='"' || *c
<' ' || (*c
=='\\' && c
[1]=='(')) {
2336 /* ?string1?string2?
2337 ** test if string1 equals string2.
2339 char *st1
=NULL
, *st2
=NULL
, *h
;
2349 while (*c
!= sep
&& (!tcmp
|| qstrncmp(c
,tcmp
,4))) c
++;
2351 scan_troff(h
, 1, &st1
);
2356 while (*c
!=sep
&& (!tcmp
|| qstrncmp(c
,tcmp
,4))) c
++;
2358 scan_troff(h
,1,&st2
);
2360 if (!st1
&& !st2
) value
=1;
2361 else if (!st1
|| !st2
) value
=0;
2362 else value
=(!qstrcmp(st1
, st2
));
2368 while (*c
&& ( !isspace(*c
) || ( numLoop
> 0 ) ) && *c
!=')' && opex
>= 0) {
2372 c
= scan_expression( c
+ 1, &value2
, numLoop
+ 1 );
2381 case '8': case '9': {
2384 while (isdigit(*c
)) value2
=value2
*10+((*c
++)-'0');
2385 if (*c
=='.' && isdigit(c
[1])) {
2387 while (isdigit(*c
)) {
2388 num
=num
*10+((*c
++)-'0');
2393 /* scale indicator */
2395 case 'i': /* inch -> 10pt */
2396 value2
=value2
*10+(num
*10+denum
/2)/denum
;
2404 value2
=value2
+(num
+denum
/2)/denum
;
2414 value2
=intresult
*sign
;
2415 if (isalpha(*c
)) c
++; /* scale indicator */
2419 if (oper
) { sign
=-1; c
++; break; }
2429 if (c
[1]=='=') oper
=(*c
++) +16; else oper
=*c
;
2432 default: c
++; break;
2437 case 'c': value
=value2
; break;
2438 case '-': value
=value
-value2
; break;
2439 case '+': value
=value
+value2
; break;
2440 case '*': value
=value
*value2
; break;
2441 case '/': if (value2
) value
=value
/value2
; break;
2442 case '%': if (value2
) value
=value
%value2
; break;
2443 case '<': value
=(value
<value2
); break;
2444 case '>': value
=(value
>value2
); break;
2445 case '>'+16: value
=(value
>=value2
); break;
2446 case '<'+16: value
=(value
<=value2
); break;
2447 case '=': case '='+16: value
=(value
==value2
); break;
2448 case '&': value
= (value
&& value2
); break;
2449 case ':': value
= (value
|| value2
); break;
2452 kDebug(7107) << "Unknown operator " << char(oper
);
2464 static char *scan_expression(char *c
, int *result
)
2466 return scan_expression( c
, result
, 0 );
2469 static void trans_char(char *c
, char s
, char t
)
2473 while (*sl
!='\n' || slash
) {
2484 // 2004-10-19, patched by Waldo Bastian <bastian@kde.org>:
2485 // Fix handling of lines like:
2486 // .TH FIND 1L \" -*- nroff -*-
2487 // Where \" indicates the start of comment.
2489 // The problem is the \" handling in fill_words(), the return value
2490 // indicates the end of the word as well as the end of the line, which makes it
2491 // basically impossible to express that the end of the last word is not the end of
2494 // I have corrected that by adding an extra parameter 'next_line' that returns a
2495 // pointer to the next line, while the function itself returns a pointer to the end
2496 // of the last word.
2497 static char *fill_words(char *c
, char *words
[], int *n
, bool newline
, char **next_line
)
2504 while (*sl
&& (*sl
!='\n' || slash
)) {
2507 if (skipspace
&& (*(sl
+1)=='"'))
2511 skipspace
=!skipspace
;
2513 } else if (*sl
==escapesym
) {
2517 } else if ((*sl
==' ' || *sl
=='\t') && !skipspace
) {
2518 if (newline
) *sl
='\n';
2519 if (words
[*n
]!=sl
) (*n
)++;
2525 if (newline
) *sl
='\n';
2526 if (words
[*n
]!=sl
) (*n
)++;
2531 while (*sl
&& *sl
!='\n') sl
++;
2541 if (sl
!=words
[*n
]) (*n
)++;
2542 if (next_line
) *next_line
= sl
+1;
2546 static const char *abbrev_list
[] = {
2547 "GSBG", "Getting Started ",
2548 "SUBG", "Customizing SunOS",
2549 "SHBG", "Basic Troubleshooting",
2550 "SVBG", "SunView User's Guide",
2551 "MMBG", "Mail and Messages",
2552 "DMBG", "Doing More with SunOS",
2553 "UNBG", "Using the Network",
2554 "GDBG", "Games, Demos & Other Pursuits",
2555 "CHANGE", "SunOS 4.1 Release Manual",
2556 "INSTALL", "Installing SunOS 4.1",
2557 "ADMIN", "System and Network Administration",
2558 "SECUR", "Security Features Guide",
2559 "PROM", "PROM User's Manual",
2560 "DIAG", "Sun System Diagnostics",
2561 "SUNDIAG", "Sundiag User's Guide",
2562 "MANPAGES", "SunOS Reference Manual",
2563 "REFMAN", "SunOS Reference Manual",
2564 "SSI", "Sun System Introduction",
2565 "SSO", "System Services Overview",
2566 "TEXT", "Editing Text Files",
2567 "DOCS", "Formatting Documents",
2568 "TROFF", "Using <B>nroff</B> and <B>troff</B>",
2569 "INDEX", "Global Index",
2570 "CPG", "C Programmer's Guide",
2571 "CREF", "C Reference Manual",
2572 "ASSY", "Assembly Language Reference",
2573 "PUL", "Programming Utilities and Libraries",
2574 "DEBUG", "Debugging Tools",
2575 "NETP", "Network Programming",
2576 "DRIVER", "Writing Device Drivers",
2577 "STREAMS", "STREAMS Programming",
2578 "SBDK", "SBus Developer's Kit",
2579 "WDDS", "Writing Device Drivers for the SBus",
2580 "FPOINT", "Floating-Point Programmer's Guide",
2581 "SVPG", "SunView 1 Programmer's Guide",
2582 "SVSPG", "SunView 1 System Programmer's Guide",
2583 "PIXRCT", "Pixrect Reference Manual",
2584 "CGI", "SunCGI Reference Manual",
2585 "CORE", "SunCore Reference Manual",
2586 "4ASSY", "Sun-4 Assembly Language Reference",
2587 "SARCH", "<FONT SIZE=\"-1\">SPARC</FONT> Architecture Manual",
2588 "KR", "The C Programming Language",
2591 static const char *lookup_abbrev(char *c
)
2596 while (abbrev_list
[i
] && qstrcmp(c
,abbrev_list
[i
])) i
=i
+2;
2597 if (abbrev_list
[i
]) return abbrev_list
[i
+1];
2601 static const char *section_list
[] = {
2604 "1", "User Commands",
2605 "1B", "SunOS/BSD Compatibility Package Commands",
2606 "1b", "SunOS/BSD Compatibility Package Commands",
2607 "1C", "Communication Commands ",
2608 "1c", "Communication Commands",
2609 "1F", "FMLI Commands ",
2610 "1f", "FMLI Commands",
2611 "1G", "Graphics and CAD Commands ",
2612 "1g", "Graphics and CAD Commands ",
2613 "1M", "Maintenance Commands",
2614 "1m", "Maintenance Commands",
2615 "1S", "SunOS Specific Commands",
2616 "1s", "SunOS Specific Commands",
2617 "2", "System Calls",
2618 "3", "C Library Functions",
2619 "3B", "SunOS/BSD Compatibility Library Functions",
2620 "3b", "SunOS/BSD Compatibility Library Functions",
2621 "3C", "C Library Functions",
2622 "3c", "C Library Functions",
2623 "3E", "C Library Functions",
2624 "3e", "C Library Functions",
2625 "3F", "Fortran Library Routines",
2626 "3f", "Fortran Library Routines",
2627 "3G", "C Library Functions",
2628 "3g", "C Library Functions",
2629 "3I", "Wide Character Functions",
2630 "3i", "Wide Character Functions",
2631 "3K", "Kernel VM Library Functions",
2632 "3k", "Kernel VM Library Functions",
2633 "3L", "Lightweight Processes Library",
2634 "3l", "Lightweight Processes Library",
2635 "3M", "Mathematical Library",
2636 "3m", "Mathematical Library",
2637 "3N", "Network Functions",
2638 "3n", "Network Functions",
2639 "3R", "Realtime Library",
2640 "3r", "Realtime Library",
2641 "3S", "Standard I/O Functions",
2642 "3s", "Standard I/O Functions",
2643 "3T", "Threads Library",
2644 "3t", "Threads Library",
2645 "3W", "C Library Functions",
2646 "3w", "C Library Functions",
2647 "3X", "Miscellaneous Library Functions",
2648 "3x", "Miscellaneous Library Functions",
2649 "4", "File Formats",
2650 "4B", "SunOS/BSD Compatibility Package File Formats",
2651 "4b", "SunOS/BSD Compatibility Package File Formats",
2652 "5", "Headers, Tables, and Macros",
2653 "6", "Games and Demos",
2654 "7", "Special Files",
2655 "7B", "SunOS/BSD Compatibility Special Files",
2656 "7b", "SunOS/BSD Compatibility Special Files",
2657 "8", "Maintenance Procedures",
2658 "8C", "Maintenance Procedures",
2659 "8c", "Maintenance Procedures",
2660 "8S", "Maintenance Procedures",
2661 "8s", "Maintenance Procedures",
2663 "9E", "DDI and DKI Driver Entry Points",
2664 "9e", "DDI and DKI Driver Entry Points",
2665 "9F", "DDI and DKI Kernel Functions",
2666 "9f", "DDI and DKI Kernel Functions",
2667 "9S", "DDI and DKI Data Structures",
2668 "9s", "DDI and DKI Data Structures",
2669 "L", "Local Commands",
2670 #elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
2671 "1", "General Commands",
2672 "2", "System Calls",
2673 "3", "Library Functions",
2674 "4", "Kernel Interfaces",
2675 "5", "File Formats",
2677 "7", "Miscellaneous Information",
2678 "8", "System Manager's Manuals",
2679 "9", "Kernel Developer's Manuals",
2682 "1", "User Commands ",
2683 "1C", "User Commands",
2684 "1G", "User Commands",
2685 "1S", "User Commands",
2686 "1V", "User Commands ",
2687 "2", "System Calls",
2688 "2V", "System Calls",
2689 "3", "C Library Functions",
2690 "3C", "Compatibility Functions",
2691 "3F", "Fortran Library Routines",
2692 "3K", "Kernel VM Library Functions",
2693 "3L", "Lightweight Processes Library",
2694 "3M", "Mathematical Library",
2695 "3N", "Network Functions",
2696 "3R", "RPC Services Library",
2697 "3S", "Standard I/O Functions",
2698 "3V", "C Library Functions",
2699 "3X", "Miscellaneous Library Functions",
2700 "4", "Devices and Network Interfaces",
2701 "4F", "Protocol Families",
2702 "4I", "Devices and Network Interfaces",
2703 "4M", "Devices and Network Interfaces",
2704 "4N", "Devices and Network Interfaces",
2706 "4S", "Devices and Network Interfaces",
2707 "4V", "Devices and Network Interfaces",
2708 "5", "File Formats",
2709 "5V", "File Formats",
2710 "6", "Games and Demos",
2711 "7", "Environments, Tables, and Troff Macros",
2712 "7V", "Environments, Tables, and Troff Macros",
2713 "8", "Maintenance Commands",
2714 "8C", "Maintenance Commands",
2715 "8S", "Maintenance Commands",
2716 "8V", "Maintenance Commands",
2717 "L", "Local Commands",
2720 NULL
, "Misc. Reference Manual Pages",
2724 static const char *section_name(char *c
)
2729 while (section_list
[i
] && qstrcmp(c
,section_list
[i
])) i
=i
+2;
2730 if (section_list
[i
+1]) return section_list
[i
+1];
2734 static char *skip_till_newline(char *c
)
2738 while (*c
&& (*c
!='\n' || lvl
>0)) {
2741 if (*c
=='}') lvl
--; else if (*c
=='{') lvl
++;
2746 if (lvl
<0 && newline_for_fun
) {
2747 newline_for_fun
= newline_for_fun
+lvl
;
2748 if (newline_for_fun
<0) newline_for_fun
=0;
2753 static bool s_whileloop
= false;
2755 /// Processing the .while request
2756 static void request_while( char*& c
, int j
, bool mdoc
)
2758 // ### TODO: .break and .continue
2759 kDebug(7107) << "Entering .while";
2761 char* newline
= skip_till_newline( c
);
2762 const char oldchar
= *newline
;
2764 // We store the full .while stuff into a QCString as if it would be a macro
2765 const QByteArray macro
= c
;
2766 kDebug(7107) << "'Macro' of .while"<< BYTEARRAY( macro
);
2767 // Prepare for continuing after .while loop end
2770 // Process -while loop
2771 const bool oldwhileloop
= s_whileloop
;
2773 int result
= true; // It must be an int due to the call to scan_expression
2776 // Unlike for a normal macro, we have the condition at start, so we do not need to prepend extra bytes
2777 char* liveloop
= qstrdup( macro
.data() );
2778 kDebug(7107) << "Scanning .while condition";
2779 kDebug(7101) << "Loop macro " << liveloop
;
2780 char* end_expression
= scan_expression( liveloop
, &result
);
2781 kDebug(7101) << "After " << end_expression
;
2784 kDebug(7107) << "New .while iteration";
2785 // The condition is true, so call the .while's content
2786 char* help
= end_expression
+ 1;
2787 while ( *help
&& ( *help
== ' ' || *help
== '\t' ) )
2791 // We have a problem, so stop .while
2796 scan_troff_mandoc( help
, false, 0 );
2798 scan_troff( help
, false, 0 );
2804 s_whileloop
= oldwhileloop
;
2805 kDebug(7107) << "Ending .while";
2808 const int max_wordlist
= 100;
2810 /// Processing mixed fonts reqiests like .BI
2811 static void request_mixed_fonts( char*& c
, int j
, const char* font1
, const char* font2
, const bool mode
, const bool inFMode
)
2816 char *wordlist
[max_wordlist
];
2817 fill_words(c
, wordlist
, &words
, true, &c
);
2818 for (int i
=0; i
<words
; i
++)
2820 if ((mode
) || (inFMode
))
2825 wordlist
[i
][-1]=' ';
2826 out_html( set_font( (i
&1) ? font2
: font1
) );
2827 scan_troff(wordlist
[i
],1,NULL
);
2829 out_html(set_font("R"));
2842 // Some known missing requests from man(7):
2843 // - see "safe subset": .tr
2845 // Some known missing requests from mdoc(7):
2846 // - start or end of quotings
2848 // Some of the requests are from mdoc.
2849 // On Linux see the man pages mdoc(7), mdoc.samples(7) and groff_mdoc(7)
2850 // See also the online man pages of FreeBSD: mdoc(7)
2852 #define REQ_UNKNOWN -1
2866 #define REQ_ft 13 // groff(7) "FonT"
2894 #define REQ_IP 41 // man(7) "Indent Paragraph"
2911 #define REQ_SH 58 // man(7) "Sub Header"
2921 #define REQ_nr 68 // groff(7) "Number Register"
2924 #define REQ_Bl 71 // mdoc(7) "Begin List"
2925 #define REQ_El 72 // mdoc(7) "End List"
2926 #define REQ_It 73 // mdoc(7) "ITem"
2930 #define REQ_Os 77 // mdoc(7)
2932 #define REQ_At 79 // mdoc(7) "AT&t" (not parsable, not callable)
2933 #define REQ_Fx 80 // mdoc(7) "Freebsd" (not parsable, not callable)
2936 #define REQ_Bx 83 // mdoc(7) "Bsd"
2937 #define REQ_Ux 84 // mdoc(7) "UniX"
2942 #define REQ_Xr 89 // mdoc(7) "eXternal Reference"
2943 #define REQ_Fl 90 // mdoc(7) "FLag"
2947 #define REQ_Dq 94 // mdoc(7) "Double Quote"
2951 #define REQ_Pq 98 // mdoc(7) "Parenthese Quote"
2953 #define REQ_Sq 100 // mdoc(7) "Single Quote"
2956 #define REQ_Em 103 // mdoc(7) "EMphasis"
2975 #define REQ_perc_A 122
2976 #define REQ_perc_D 123
2977 #define REQ_perc_N 124
2978 #define REQ_perc_O 125
2979 #define REQ_perc_P 126
2980 #define REQ_perc_Q 127
2981 #define REQ_perc_V 128
2982 #define REQ_perc_B 129
2983 #define REQ_perc_J 130
2984 #define REQ_perc_R 131
2985 #define REQ_perc_T 132
2986 #define REQ_An 133 // mdoc(7) "Author Name"
2987 #define REQ_Aq 134 // mdoc(7) "Angle bracket Quote"
2988 #define REQ_Bq 135 // mdoc(7) "Bracket Quote"
2989 #define REQ_Qq 136 // mdoc(7) "straight double Quote"
2990 #define REQ_UR 137 // man(7) "URl"
2991 #define REQ_UE 138 // man(7) "Url End"
2992 #define REQ_UN 139 // man(7) "Url Name" (a.k.a. anchors)
2993 #define REQ_troff 140 // groff(7) "TROFF mode"
2994 #define REQ_nroff 141 // groff(7) "NROFF mode"
2995 #define REQ_als 142 // groff(7) "ALias String"
2996 #define REQ_rr 143 // groff(7) "Remove number Register"
2997 #define REQ_rnn 144 // groff(7) "ReName Number register"
2998 #define REQ_aln 145 // groff(7) "ALias Number register"
2999 #define REQ_shift 146 // groff(7) "SHIFT parameter"
3000 #define REQ_while 147 // groff(7) "WHILE loop"
3001 #define REQ_do 148 // groff(7) "DO command"
3002 #define REQ_Dx 149 // mdoc(7) "DragonFly" macro
3004 static int get_request(char *req
, int len
)
3006 static const char *requests
[] = {
3007 "ab", "di", "ds", "as", "br", "c2", "cc", "ce", "ec", "eo", "ex", "fc",
3008 "fi", "ft", "el", "ie", "if", "ig", "nf", "ps", "sp", "so", "ta", "ti",
3009 "tm", "B", "I", "Fd", "Fn", "Fo", "Fc", "OP", "Ft", "Fa", "BR", "BI",
3010 "IB", "IR", "RB", "RI", "DT", "IP", "TP", "IX", "P", "LP", "PP", "HP",
3011 "PD", "Rs", "RS", "Re", "RE", "SB", "SM", "Ss", "SS", "Sh", "SH", "Sx",
3012 "TS", "Dt", "TH", "TX", "rm", "rn", "nx", "in", "nr", "am", "de", "Bl",
3013 "El", "It", "Bk", "Ek", "Dd", "Os", "Bt", "At", "Fx", "Nx", "Ox", "Bx",
3014 "Ux", "Dl", "Bd", "Ed", "Be", "Xr", "Fl", "Pa", "Pf", "Pp", "Dq", "Op",
3015 "Oo", "Oc", "Pq", "Ql", "Sq", "Ar", "Ad", "Em", "Va", "Xc", "Nd", "Nm",
3016 "Cd", "Cm", "Ic", "Ms", "Or", "Sy", "Dv", "Ev", "Fr", "Li", "No", "Ns",
3017 "Tn", "nN", "%A", "%D", "%N", "%O", "%P", "%Q", "%V", "%B", "%J", "%R",
3018 "%T", "An", "Aq", "Bq", "Qq", "UR", "UE", "UN", "troff", "nroff", "als",
3019 "rr", "rnn", "aln", "shift", "while", "do", "Dx", 0 };
3021 while (requests
[r
] && qstrncmp(req
, requests
[r
], len
)) r
++;
3022 return requests
[r
] ? r
: REQ_UNKNOWN
;
3025 // &%(#@ c programs !!!
3026 //static int ifelseval=0;
3027 // If/else can be nested!
3028 static QStack
<int> s_ifelseval
;
3030 // Process a (mdoc) request involving quotes
3031 static char* process_quote(char* c
, int j
, const char* open
, const char* close
)
3033 trans_char(c
,'"','\a');
3035 if (*c
=='\n') c
++; // ### TODO: why? Quote requests cannot be empty!
3037 c
=scan_troff_mandoc(c
,1,0);
3048 * Is the char \p ch a puntuaction in sence of mdoc(7)
3050 static bool is_mdoc_punctuation( const char ch
)
3052 if ( ( ch
>= '0' && ch
<= '9' ) || ( ch
>='A' && ch
<='Z' ) || ( ch
>= 'a' && ch
<= 'z' ) )
3054 else if ( ch
== '.' || ch
== ',' || ch
== ';' || ch
== ':' || ch
== '(' || ch
== ')'
3055 || ch
== '[' || ch
== ']' )
3062 * Can the char \p c be part of an identifier
3063 * \note For groff, an identifier can consist of nearly all ASCII printable non-white-space characters
3064 * See info:/groff/Identifiers
3066 static bool is_identifier_char( const char c
)
3068 if ( c
>= '!' && c
<= '[' ) // Include digits and upper case
3070 else if ( c
>= ']' && c
<= '~' ) // Include lower case
3072 else if ( c
== '\\' )
3073 return false; // ### TODO: it should be treated as escape instead!
3077 static QByteArray
scan_identifier( char*& c
)
3079 char* h
= c
; // help pointer
3080 // ### TODO Groff seems to eat nearly everything as identifier name (info:/groff/Identifiers)
3081 while ( *h
&& *h
!= '\a' && *h
!= '\n' && is_identifier_char( *h
) )
3083 const char tempchar
= *h
;
3085 const QByteArray name
= c
;
3087 if ( name
.isEmpty() )
3089 kDebug(7107) << "EXCEPTION: identifier empty!";
3095 static char *scan_request(char *c
)
3098 static bool mandoc_synopsis
=false; /* True if we are in the synopsis section */
3099 static bool mandoc_command
=false; /* True if this is mdoc(7) page */
3100 static int mandoc_bd_options
; /* Only copes with non-nested Bd's */
3101 static int function_argument
=0; // Number of function argument (.Fo, .Fa, .Fc)
3103 static bool ur_ignore
=false; // Has .UR a parameter : (for .UE to know if or not to write </a>)
3108 char *wordlist
[max_wordlist
];
3111 while (*c
==' ' || *c
=='\t') c
++; // Spaces or tabs allowed between control character and request
3112 if (c
[0]=='\n') return c
+1;
3113 if (c
[0]==escapesym
)
3115 /* some pages use .\" .\$1 .\} */
3116 /* .\$1 is too difficult/stuppid */
3119 kDebug(7107) << "Found .\\$";
3120 c
=skip_till_newline(c
); // ### TODO
3124 c
= scan_escape(c
+1);
3129 QByteArray macroName
;
3130 while (c
[nlen
] && (c
[nlen
] != ' ') && (c
[nlen
] != '\t') && (c
[nlen
] != '\n') && (c
[nlen
] != escapesym
))
3136 while (c
[j
]==' ' || c
[j
]=='\t') j
++;
3137 /* search macro database of self-defined macros */
3138 QMap
<QByteArray
,StringDefinition
>::const_iterator it
=s_stringDefinitionMap
.find(macroName
);
3139 if (it
!=s_stringDefinitionMap
.end())
3141 kDebug(7107) << "CALLING MACRO: " << BYTEARRAY( macroName
);
3142 const QByteArray oldDollarZero
= s_dollarZero
; // Previous value of $0
3143 s_dollarZero
= macroName
;
3144 sl
=fill_words(c
+j
, wordlist
, &words
, true, &c
);
3146 for (i
=1;i
<words
; i
++) wordlist
[i
][-1]='\0';
3147 for (i
=0; i
<words
; i
++)
3151 scan_troff_mandoc(wordlist
[i
],1,&h
);
3153 scan_troff(wordlist
[i
],1,&h
);
3154 wordlist
[i
] = qstrdup(h
);
3157 for ( i
=words
; i
<max_wordlist
; i
++ ) wordlist
[i
]=NULL
;
3158 if ( !(*it
).m_output
.isEmpty() )
3160 //kDebug(7107) << "Macro content is: "<< BYTEARRAY( (*it).m_output );
3161 const unsigned int length
= (*it
).m_output
.length();
3162 char* work
= new char [length
+2];
3163 work
[0] = '\n'; // The macro must start after an end of line to allow a request on first line
3164 qstrncpy(work
+1,(*it
).m_output
.data(),length
+1);
3165 const QList
<char*> oldArgumentList( s_argumentList
);
3166 s_argumentList
.clear();
3167 for ( i
= 0 ; i
< max_wordlist
; i
++ )
3171 s_argumentList
.push_back( wordlist
[i
] );
3173 const int onff
=newline_for_fun
;
3175 scan_troff_mandoc( work
+ 1, 0, NULL
);
3177 scan_troff( work
+ 1, 0, NULL
);
3179 newline_for_fun
=onff
;
3180 s_argumentList
= oldArgumentList
;
3182 for (i
=0; i
<words
; i
++) delete [] wordlist
[i
];
3184 s_dollarZero
= oldDollarZero
;
3185 kDebug(7107) << "ENDING MACRO: " << BYTEARRAY( macroName
);
3189 kDebug(7107) << "REQUEST: " << BYTEARRAY( macroName
);
3190 switch (int request
= get_request(c
, nlen
))
3192 case REQ_ab
: // groff(7) "ABort"
3195 while (*h
&& *h
!='\n') h
++;
3197 if (scaninbuff
&& buffpos
)
3199 buffer
[buffpos
]='\0';
3200 kDebug(7107) << "ABORT: " << buffer
;
3202 // ### TODO find a way to display it to the user
3203 kDebug(7107) << "Aborting: .ab " << (c
+j
);
3207 case REQ_An
: // mdoc(7) "Author Name"
3210 c
=scan_troff_mandoc(c
,1,0);
3213 case REQ_di
: // groff(7) "end current DIversion"
3215 kDebug(7107) << "Start .di";
3222 const QByteArray
name ( scan_identifier( c
) );
3223 while (*c
&& *c
!='\n') c
++;
3226 while (*c
&& qstrncmp(c
,".di",3)) while (*c
&& *c
++!='\n');
3229 scan_troff(h
,0,&result
);
3230 QMap
<QByteArray
,StringDefinition
>::iterator it
=s_stringDefinitionMap
.find(name
);
3231 if (it
==s_stringDefinitionMap
.end())
3233 StringDefinition def
;
3235 def
.m_output
=result
;
3236 s_stringDefinitionMap
.insert(name
,def
);
3241 (*it
).m_output
=result
;
3245 c
=skip_till_newline(c
);
3246 kDebug(7107) << "end .di";
3249 case REQ_ds
: // groff(7) "Define String variable"
3251 case REQ_as
: // groff (7) "Append String variable"
3253 kDebug(7107) << "start .ds/.as";
3254 int oldcurpos
=curpos
;
3256 const QByteArray
name( scan_identifier( c
) );
3257 if ( name
.isEmpty() )
3259 while (*c
&& isspace(*c
)) c
++;
3260 if (*c
&& *c
=='"') c
++;
3264 c
=scan_troff(c
,1,&result
);
3265 QMap
<QByteArray
,StringDefinition
>::iterator it
=s_stringDefinitionMap
.find(name
);
3266 if (it
==s_stringDefinitionMap
.end())
3268 StringDefinition def
;
3269 def
.m_length
=curpos
;
3270 def
.m_output
=result
;
3271 s_stringDefinitionMap
.insert(name
,def
);
3276 { // .ds Defining String
3277 (*it
).m_length
=curpos
;
3278 (*it
).m_output
=result
;
3281 { // .as Appending String
3282 (*it
).m_length
+=curpos
;
3283 (*it
).m_output
+=result
;
3287 single_escape
=false;
3289 kDebug(7107) << "end .ds/.as";
3292 case REQ_br
: // groff(7) "line BReak"
3295 out_html("<DD>"); // ### VERIFY (does not look like generating good HTML)
3300 if (c
[0]==escapesym
) c
=scan_escape(c
+1);
3301 c
=skip_till_newline(c
);
3304 case REQ_c2
: // groff(7) "reset non-break Control character" (2 means non-break)
3311 c
=skip_till_newline(c
);
3314 case REQ_cc
: // groff(7) "reset Control Character"
3321 c
=skip_till_newline(c
);
3324 case REQ_ce
: // groff (7) "CEnter"
3332 while ('0'<=*c
&& *c
<='9')
3338 c
=skip_till_newline(c
);
3339 /* center next i lines */
3342 out_html("<CENTER>\n");
3346 c
=scan_troff(c
,1, &line
);
3347 if (line
&& qstrncmp(line
, "<BR>", 4))
3351 delete [] line
; // ### FIXME: memory leak!
3355 out_html("</CENTER>\n");
3360 case REQ_ec
: // groff(7) "reset Escape Character"
3368 c
=skip_till_newline(c
);
3370 case REQ_eo
: // groff(7) "turn Escape character Off"
3373 c
=skip_till_newline(c
);
3376 case REQ_ex
: // groff(7) "EXit"
3381 case REQ_fc
: // groff(7) "set Field and pad Character"
3385 fieldsym
=padsym
='\0';
3391 c
=skip_till_newline(c
);
3394 case REQ_fi
: // groff(7) "FIll"
3398 out_html(set_font("R"));
3399 out_html(change_to_size('0'));
3400 out_html("</PRE>\n");
3404 c
=skip_till_newline(c
);
3407 case REQ_ft
: // groff(7) "FonT"
3410 h
= skip_till_newline( c
);
3411 const char oldChar
= *h
;
3413 const QByteArray name
= c
;
3414 // ### TODO: name might contain a variable
3415 if ( name
.isEmpty() )
3416 out_html( set_font( "P" ) ); // Previous font
3418 out_html( set_font( name
) );
3423 case REQ_el
: // groff(7) "ELse"
3425 int ifelseval
= s_ifelseval
.pop();
3426 /* .el anything : else part of if else */
3431 c
=scan_troff(c
,1,NULL
);
3434 c
=skip_till_newline(c
+j
);
3437 case REQ_ie
: // groff(7) "If with Else"
3438 /* .ie c anything : then part of if else */
3439 case REQ_if
: // groff(7) "IF"
3445 * .if 'string1'string2' anything
3446 * .if !'string1'string2' anything
3449 c
=scan_expression(c
, &i
);
3450 if (request
== REQ_ie
)
3453 s_ifelseval
.push( ifelseval
);
3459 c
=scan_troff(c
,1,NULL
);
3462 c
=skip_till_newline(c
);
3465 case REQ_ig
: // groff(7) "IGnore"
3467 const char *endwith
="..\n";
3470 if (*c
!='\n' && *c
!= '\\')
3472 /* Not newline or comment */
3475 while (*c
&& *c
!='\n') c
++,i
++;
3478 while (*c
&& qstrncmp(c
,endwith
,i
)) while (*c
++!='\n');
3479 while (*c
&& *c
++!='\n');
3482 case REQ_nf
: // groff(7) "No Filling"
3486 out_html(set_font("R"));
3487 out_html(change_to_size('0'));
3488 out_html("<PRE>\n");
3492 c
=skip_till_newline(c
);
3495 case REQ_ps
: // groff(7) "previous Point Size"
3499 out_html(change_to_size('0'));
3510 c
=scan_expression(c
, &i
);
3516 out_html(change_to_size(i
*j
));
3518 c
=skip_till_newline(c
);
3521 case REQ_sp
: // groff(7) "SKip one line"
3525 out_html("<br><br>");
3531 c
=skip_till_newline(c
);
3534 case REQ_so
: // groff(7) "Include SOurce file"
3549 while (*c
!='\n') c
++;
3551 scan_troff(h
,1, &name
);
3556 /* this works alright, except for section 3 */
3557 buf
=read_man_page(h
);
3560 kDebug(7107) << "Unable to open or read file: .so " << (h
);
3561 out_html("<BLOCKQUOTE>"
3562 "man2html: unable to open or read file.\n");
3564 out_html("</BLOCKQUOTE>\n");
3567 scan_troff(buf
+1,0,NULL
);
3574 case REQ_ta
: // gorff(7) "set TAbulators"
3580 sl
=scan_expression(c
, &tabstops
[j
]);
3581 if (j
>0 && (*c
=='-' || *c
=='+')) tabstops
[j
]+=tabstops
[j
-1];
3583 while (*c
==' ' || *c
=='\t') c
++;
3590 case REQ_ti
: // groff(7) "Temporary Indent"
3592 /*while (itemdepth || dl_set[itemdepth]) {
3593 out_html("</DL>\n");
3594 if (dl_set[itemdepth]) dl_set[itemdepth]=0;
3599 c
=scan_expression(c
, &j
);
3600 for (i
=0; i
<j
; i
++) out_html(" ");
3602 c
=skip_till_newline(c
);
3605 case REQ_tm
: // groff(7) "TerMinal" ### TODO: what are useful uses for it
3609 while (*c
!='\n') c
++;
3611 kDebug(7107) << ".tm " << (h
);
3615 case REQ_B
: // man(7) "Bold"
3617 case REQ_I
: // man(7) "Italic"
3619 /* parse one line in a certain font */
3620 out_html( set_font( mode
?"B":"I" ) );
3621 fill_words(c
, wordlist
, &words
, false, 0);
3624 c
=scan_troff(c
, 1, NULL
);
3625 out_html(set_font("R"));
3633 case REQ_Fd
: // mdoc(7) "Function Definition"
3635 // Normal text must be printed in bold, punctuation in regular font
3637 if (*c
=='\n') c
++; // ### TODO: verify
3638 sl
=fill_words(c
, wordlist
, &words
, true, &c
);
3639 for (i
=0; i
<words
; i
++)
3641 wordlist
[i
][-1]=' ';
3642 // ### FIXME In theory, only a single punctuation character is recognized as punctuation
3643 if ( is_mdoc_punctuation ( *wordlist
[i
] ) )
3644 out_html( set_font ( "R" ) );
3646 out_html( set_font ( "B" ) );
3647 scan_troff(wordlist
[i
],1,NULL
);
3650 // In the mdoc synopsis, there are automatical line breaks (### TODO: before or after?)
3651 if (mandoc_synopsis
)
3655 out_html(set_font("R"));
3663 case REQ_Fn
: // mdoc(7) for "Function calls"
3665 // brackets and commas have to be inserted automatically
3668 sl
=fill_words(c
, wordlist
, &words
, true, &c
);
3671 for (i
=0; i
<words
; i
++)
3673 wordlist
[i
][-1]=' ';
3675 out_html( set_font( "I" ) );
3677 out_html( set_font( "B" ) );
3678 scan_troff(wordlist
[i
],1,NULL
);
3679 out_html( set_font( "R" ) );
3689 out_html(set_font("R"));
3690 if (mandoc_synopsis
)
3699 case REQ_Fo
: // mdoc(7) "Function definition Opening"
3701 char* font
[2] = { (char*)"B", (char*)"R" };
3704 char *eol
=strchr(c
,'\n');
3705 char *semicolon
=strchr(c
,';');
3706 if ((semicolon
!=0) && (semicolon
<eol
)) *semicolon
=' ';
3708 sl
=fill_words(c
, wordlist
, &words
, true, &c
);
3709 // Normally a .Fo has only one parameter
3710 for (i
=0; i
<words
; i
++)
3712 wordlist
[i
][-1]=' ';
3713 out_html(set_font(font
[i
&1]));
3714 scan_troff(wordlist
[i
],1,NULL
);
3719 // ### TODO What should happen if there is more than one argument
3720 // else if (i<words-1) out_html(", ");
3722 function_argument
=1; // Must be > 0
3723 out_html(set_font("R"));
3731 case REQ_Fc
:// mdoc(7) "Function definition Close"
3733 // .Fc has no parameter
3735 c
=skip_till_newline(c
);
3736 char* font
[2] = { (char*)"B", (char*)"R" };
3737 out_html(set_font(font
[i
&1]));
3739 out_html(set_font("R"));
3740 if (mandoc_synopsis
)
3747 function_argument
=0; // Reset the count variable
3750 case REQ_Fa
: // mdoc(7) "Function definition argument"
3752 char* font
[2] = { (char*)"B", (char*)"R" };
3755 sl
=fill_words(c
, wordlist
, &words
, true, &c
);
3756 out_html(set_font(font
[i
&1]));
3757 // function_argument==0 means that we had no .Fo before, e.g. in mdoc.samples(7)
3758 if (function_argument
> 1)
3762 function_argument
++;
3764 else if (function_argument
==1)
3766 // We are only at the first parameter
3767 function_argument
++;
3769 for (i
=0; i
<words
; i
++)
3771 wordlist
[i
][-1]=' ';
3772 scan_troff(wordlist
[i
],1,NULL
);
3774 out_html(set_font("R"));
3782 case REQ_OP
: /* groff manpages use this construction */
3784 /* .OP a b : [ <B>a</B> <I>b</I> ] */
3786 out_html(set_font("R"));
3789 request_mixed_fonts( c
, j
, "B", "I", true, false );
3793 case REQ_Ft
: //perhaps "Function return type"
3795 request_mixed_fonts( c
, j
, "B", "I", false, true );
3800 request_mixed_fonts( c
, j
, "B", "R", false, false );
3805 request_mixed_fonts( c
, j
, "B", "I", false, false );
3810 request_mixed_fonts( c
, j
, "I", "B", false, false );
3815 request_mixed_fonts( c
, j
, "I", "R", false, false );
3820 request_mixed_fonts( c
, j
, "R", "B", false, false );
3825 request_mixed_fonts( c
, j
, "R", "I", false, false );
3828 case REQ_DT
: // man(7) "Default Tabulators"
3830 for (j
=0;j
<20; j
++) tabstops
[j
]=(j
+1)*8;
3832 c
=skip_till_newline(c
);
3835 case REQ_IP
: // man(7) "Ident Paragraph"
3837 sl
=fill_words(c
+j
, wordlist
, &words
, true, &c
);
3838 if (!dl_set
[itemdepth
])
3841 dl_set
[itemdepth
]=1;
3845 scan_troff(wordlist
[0], 1,NULL
);
3850 case REQ_TP
: // man(7) "hanging Tag Paragraph"
3852 if (!dl_set
[itemdepth
])
3854 out_html("<br><br><DL>\n");
3855 dl_set
[itemdepth
]=1;
3858 c
=skip_till_newline(c
);
3859 /* somewhere a definition ends with '.TP' */
3865 while (c
[0]=='.' && c
[1]=='\\' && c
[2]=='\"')
3867 // We have a comment, so skip the line
3868 c
=skip_till_newline(c
);
3870 c
=scan_troff(c
,1,NULL
);
3876 case REQ_IX
: // "INdex" ### TODO: where is it defined?
3879 c
=skip_till_newline(c
);
3882 case REQ_P
: // man(7) "Paragraph"
3883 case REQ_LP
:// man(7) "Paragraph"
3884 case REQ_PP
:// man(7) "Paragraph; reset Prevailing indent"
3886 if (dl_set
[itemdepth
])
3888 out_html("</DL>\n");
3889 dl_set
[itemdepth
]=0;
3892 out_html("<br><br>\n");
3898 c
=skip_till_newline(c
);
3901 case REQ_HP
: // man(7) "Hanging indent Paragraph"
3903 if (!dl_set
[itemdepth
])
3906 dl_set
[itemdepth
]=1;
3910 c
=skip_till_newline(c
);
3914 case REQ_PD
: // man(7) "Paragraph Distance"
3916 c
=skip_till_newline(c
);
3919 case REQ_Rs
: // mdoc(7) "Relative margin Start"
3920 case REQ_RS
: // man(7) "Relative margin Start"
3922 sl
=fill_words(c
+j
, wordlist
, &words
, true, 0);
3924 if (words
>0) scan_expression(wordlist
[0], &j
);
3928 dl_set
[itemdepth
]=0;
3929 out_html("<DL><DT><DD>");
3930 c
=skip_till_newline(c
);
3935 case REQ_Re
: // mdoc(7) "Relative margin End"
3936 case REQ_RE
: // man(7) "Relative margin End"
3940 if (dl_set
[itemdepth
]) out_html("</DL>");
3941 out_html("</DL>\n");
3944 c
=skip_till_newline(c
);
3948 case REQ_SB
: // man(7) "Small; Bold"
3950 out_html(set_font("B"));
3951 out_html("<small>");
3952 trans_char(c
,'"','\a'); // ### VERIFY
3953 c
=scan_troff(c
+j
, 1, NULL
);
3954 out_html("</small>");
3955 out_html(set_font("R"));
3958 case REQ_SM
: // man(7) "SMall"
3962 out_html("<small>");
3963 trans_char(c
,'"','\a'); // ### VERIFY
3964 c
=scan_troff(c
,1,NULL
);
3965 out_html("</small>");
3968 case REQ_Ss
: // mdoc(7) "Sub Section"
3970 case REQ_SS
: // mdoc(7) "Sub Section"
3972 case REQ_Sh
: // mdoc(7) "Sub Header"
3973 /* hack for fallthru from above */
3974 mandoc_command
= !mode
|| mandoc_command
;
3975 case REQ_SH
: // man(7) "Sub Header"
3979 while (itemdepth
|| dl_set
[itemdepth
])
3981 out_html("</DL>\n");
3982 if (dl_set
[itemdepth
])
3983 dl_set
[itemdepth
]=0;
3984 else if (itemdepth
> 0)
3987 out_html(set_font("R"));
3988 out_html(change_to_size(0));
3994 trans_char(c
,'"', '\a');
3997 out_html("</div>\n");
4004 mandoc_synopsis
= qstrncmp(c
, "SYNOPSIS", 8) == 0;
4005 c
= mandoc_command
? scan_troff_mandoc(c
,1,NULL
) : scan_troff(c
,1,NULL
);
4007 out_html("</H3>\n");
4009 out_html("</H2>\n");
4010 out_html("<div>\n");
4016 case REQ_Sx
: // mdoc(7)
4018 // reference to a section header
4019 out_html(set_font("B"));
4020 trans_char(c
,'"','\a');
4023 c
=scan_troff(c
, 1, NULL
);
4024 out_html(set_font("R"));
4032 case REQ_TS
: // Table Start tbl(1)
4037 case REQ_Dt
: /* mdoc(7) */
4038 mandoc_command
= true;
4039 case REQ_TH
: // man(7) "Title Header"
4041 if (!output_possible
)
4043 sl
= fill_words(c
+j
, wordlist
, &words
, true, &c
);
4044 // ### TODO: the page should be displayed even if it is "anonymous" (words==0)
4047 for (i
=1; i
<words
; i
++) wordlist
[i
][-1]='\0';
4049 for (i
=0; i
<words
; i
++)
4051 if (wordlist
[i
][0] == '\007')
4053 if (wordlist
[i
][qstrlen(wordlist
[i
])-1] == '\007')
4054 wordlist
[i
][qstrlen(wordlist
[i
])-1] = 0;
4056 output_possible
=true;
4057 out_html( DOCTYPE
"<HTML>\n<HEAD>\n");
4058 #ifdef SIMPLE_MAN2HTML
4059 // Most English man pages are in ISO-8859-1
4060 out_html("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n");
4062 //let KEncodingDetector decide. (it should be better then charset="System")
4063 //TODO can we check if the charset could be determined from path? like share/man/ru.UTF8
4064 // kio_man transforms from local to UTF-8
4065 // out_html("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=");
4066 // out_html(QTextCodec::codecForLocale()->name());
4067 // out_html("\">\n");
4069 out_html("<TITLE>");
4070 out_html(scan_troff(wordlist
[0], 0, NULL
));
4071 out_html( " Manpage</TITLE>\n");
4072 out_html( "<link rel=\"stylesheet\" href=\"");
4074 out_html("/kde-default.css\" type=\"text/css\">\n" );
4075 out_html( "<meta name=\"ROFF Type\" content=\"");
4081 out_html( "</HEAD>\n\n" );
4082 out_html("<BODY BGCOLOR=\"#FFFFFF\">\n\n" );
4083 out_html("<div style=\"background-image: url(");
4085 out_html("/top-middle.png); width: 100%; height: 131pt;\">\n" );
4086 out_html("<div style=\"position: absolute; right: 0pt;\">\n");
4087 out_html("<img src=\"");
4089 out_html("/top-right-konqueror.png\" style=\"margin: 0pt\" alt=\"Top right\">\n");
4090 out_html("</div>\n");
4092 out_html("<div style=\"position: absolute; left: 0pt;\">\n");
4093 out_html("<img src=\"");
4095 out_html("/top-left.png\" style=\"margin: 0pt\" alt=\"Top left\">\n");
4096 out_html("</div>\n");
4097 out_html("<div style=\"position: absolute; top: 25pt; right: 100pt; text-align: right; font-size: xx-large; font-weight: bold; text-shadow: #fff 0pt 0pt 5pt; color: #444\">\n");
4098 out_html( scan_troff(wordlist
[0], 0, NULL
) );
4099 out_html("</div>\n");
4100 out_html("</div>\n");
4101 out_html("<div style=\"margin-left: 5em; margin-right: 5em;\">\n");
4103 out_html( scan_troff(wordlist
[0], 0, NULL
) );
4104 out_html( "</h1>\n" );
4107 out_html("Section: " );
4108 if (!mandoc_command
&& words
>4)
4109 out_html(scan_troff(wordlist
[4], 0, NULL
) );
4111 out_html(section_name(wordlist
[1]));
4113 out_html(scan_troff(wordlist
[1], 0, NULL
));
4118 out_html("Section not specified");
4125 kWarning(7107) << ".TH found but output not possible" ;
4126 c
=skip_till_newline(c
);
4131 case REQ_TX
: // mdoc(7)
4133 sl
=fill_words(c
+j
, wordlist
, &words
, true, &c
);
4135 out_html(set_font("I"));
4136 if (words
>1) wordlist
[1][-1]='\0';
4137 const char *c2
=lookup_abbrev(wordlist
[0]);
4138 curpos
+=qstrlen(c2
);
4140 out_html(set_font("R"));
4142 out_html(wordlist
[1]);
4146 case REQ_rm
: // groff(7) "ReMove"
4147 /* .rm xx : Remove request, macro or string */
4149 case REQ_rn
: // groff(7) "ReName"
4150 /* .rn xx yy : Rename request, macro or string xx to yy */
4152 kDebug(7107) << "start .rm/.rn";
4154 const QByteArray
name( scan_identifier( c
) );
4155 if ( name
.isEmpty() )
4157 kDebug(7107) << "EXCEPTION: empty origin string to remove/rename";
4163 while (*c
&& isspace(*c
) && *c
!='\n') ++c
;
4164 name2
= scan_identifier( c
);
4165 if ( name2
.isEmpty() )
4167 kDebug(7107) << "EXCEPTION: empty destination string to rename";
4171 c
=skip_till_newline(c
);
4172 QMap
<QByteArray
,StringDefinition
>::iterator it
=s_stringDefinitionMap
.find(name
);
4173 if (it
==s_stringDefinitionMap
.end())
4175 kDebug(7107) << "EXCEPTION: cannot find string to rename or remove: " << BYTEARRAY( name
);
4182 s_stringDefinitionMap
.remove(name
); // ### QT4: removeAll
4187 StringDefinition def
=(*it
);
4188 s_stringDefinitionMap
.remove(name
); // ### QT4: removeAll
4189 s_stringDefinitionMap
.insert(name2
,def
);
4192 kDebug(7107) << "end .rm/.rn";
4196 case REQ_in
: // groff(7) "INdent"
4198 /* .in +-N : Indent */
4199 c
=skip_till_newline(c
);
4202 case REQ_nr
: // groff(7) "Number Register"
4204 kDebug(7107) << "start .nr";
4206 const QByteArray
name( scan_identifier( c
) );
4207 if ( name
.isEmpty() )
4209 kDebug(7107) << "EXCEPTION: empty name for register variable";
4212 while ( *c
&& ( *c
==' ' || *c
=='\t' ) ) c
++;
4214 if ( *c
&& ( *c
== '+' || *c
== '-' ) )
4218 else if ( *c
== '-' )
4223 c
=scan_expression( c
, &value
);
4224 if ( *c
&& *c
!='\n')
4226 while ( *c
&& ( *c
==' ' || *c
=='\t' ) ) c
++;
4227 c
=scan_expression( c
, &increment
);
4229 c
= skip_till_newline( c
);
4230 QMap
<QByteArray
, NumberDefinition
>::iterator it
= s_numberDefinitionMap
.find( name
);
4231 if ( it
== s_numberDefinitionMap
.end() )
4235 NumberDefinition
def( value
, increment
);
4236 s_numberDefinitionMap
.insert( name
, def
);
4241 (*it
).m_value
+= value
;
4242 else if ( sign
< 0 )
4243 (*it
).m_value
+= - value
;
4245 (*it
).m_value
= value
;
4246 (*it
).m_increment
= increment
;
4248 kDebug(7107) << "end .nr";
4251 case REQ_am
: // groff(7) "Append Macro"
4252 /* .am xx yy : append to a macro. */
4253 /* define or handle as .ig yy */
4255 case REQ_de
: // groff(7) "DEfine macro"
4256 /* .de xx yy : define or redefine macro xx; end at .yy (..) */
4257 /* define or handle as .ig yy */
4259 kDebug(7107) << "Start .am/.de";
4262 sl
= fill_words(c
, wordlist
, &words
, true, &next_line
);
4263 char *nameStart
= wordlist
[0];
4265 while (*c
&& (*c
!= ' ') && (*c
!= '\n')) c
++;
4267 const QByteArray
name(nameStart
);
4269 QByteArray endmacro
;
4278 while (*c
&& (*c
!= ' ') && (*c
!= '\n'))
4283 const int length
=qstrlen(endmacro
);
4284 while (*c
&& qstrncmp(c
,endmacro
,length
))
4285 c
=skip_till_newline(c
);
4290 if (sl
[0]=='\\' && sl
[1]=='\\')
4300 QMap
<QByteArray
,StringDefinition
>::iterator it
=s_stringDefinitionMap
.find(name
);
4301 if (it
==s_stringDefinitionMap
.end())
4303 StringDefinition def
;
4306 s_stringDefinitionMap
.insert(name
,def
);
4311 (*it
).m_length
=0; // It could be formerly a string
4312 if ( ! (*it
).m_output
.endsWith( '\n' ) )
4313 (*it
).m_output
+='\n';
4314 (*it
).m_output
+=macro
;
4319 (*it
).m_length
=0; // It could be formerly a string
4320 (*it
).m_output
=macro
;
4322 c
=skip_till_newline(c
);
4323 kDebug(7107) << "End .am/.de";
4326 case REQ_Bl
: // mdoc(7) "Begin List"
4328 char list_options
[NULL_TERMINATED(MED_STR_MAX
)];
4329 char *nl
= strchr(c
,'\n');
4331 if (dl_set
[itemdepth
])
4332 /* These things can nest. */
4336 /* Parse list options */
4337 strlimitcpy(list_options
, c
, nl
- c
, MED_STR_MAX
);
4339 if (strstr(list_options
, "-bullet"))
4341 /* HTML Unnumbered List */
4342 dl_set
[itemdepth
] = BL_BULLET_LIST
;
4345 else if (strstr(list_options
, "-enum"))
4347 /* HTML Ordered List */
4348 dl_set
[itemdepth
] = BL_ENUM_LIST
;
4353 /* HTML Descriptive List */
4354 dl_set
[itemdepth
] = BL_DESC_LIST
;
4358 out_html("<br><br>\n");
4364 c
=skip_till_newline(c
);
4367 case REQ_El
: // mdoc(7) "End List"
4370 if (dl_set
[itemdepth
] & BL_DESC_LIST
)
4371 out_html("</DL>\n");
4372 else if (dl_set
[itemdepth
] & BL_BULLET_LIST
)
4373 out_html("</UL>\n");
4374 else if (dl_set
[itemdepth
] & BL_ENUM_LIST
)
4375 out_html("</OL>\n");
4376 dl_set
[itemdepth
]=0;
4377 if (itemdepth
> 0) itemdepth
--;
4379 out_html("<br><br>\n");
4385 c
=skip_till_newline(c
);
4388 case REQ_It
: // mdoc(7) "list ITem"
4391 if (qstrncmp(c
, "Xo", 2) == 0 && isspace(*(c
+2)))
4392 c
= skip_till_newline(c
);
4393 if (dl_set
[itemdepth
] & BL_DESC_LIST
)
4396 out_html(set_font("B"));
4399 /* Don't allow embedded comms after a newline */
4401 c
=scan_troff(c
,1,NULL
);
4405 /* Do allow embedded comms on the same line. */
4406 c
=scan_troff_mandoc(c
,1,NULL
);
4408 out_html(set_font("R"));
4412 else if (dl_set
[itemdepth
] & (BL_BULLET_LIST
| BL_ENUM_LIST
))
4415 c
=scan_troff_mandoc(c
,1,NULL
);
4424 case REQ_Bk
: /* mdoc(7) */
4425 case REQ_Ek
: /* mdoc(7) */
4426 case REQ_Dd
: /* mdoc(7) */
4427 case REQ_Os
: // mdoc(7) "Operating System"
4429 trans_char(c
,'"','\a');
4432 c
=scan_troff_mandoc(c
, 1, NULL
);
4440 case REQ_Bt
: // mdoc(7) "Beta Test"
4442 trans_char(c
,'"','\a');
4444 out_html(" is currently in beta test.");
4451 case REQ_At
: /* mdoc(7) */
4452 case REQ_Fx
: /* mdoc(7) */
4453 case REQ_Nx
: /* mdoc(7) */
4454 case REQ_Ox
: /* mdoc(7) */
4455 case REQ_Bx
: /* mdoc(7) */
4456 case REQ_Ux
: /* mdoc(7) */
4457 case REQ_Dx
: /* mdoc(7) */
4460 trans_char(c
,'"','\a');
4463 if (request
==REQ_At
)
4465 out_html("AT&T UNIX ");
4468 else if (request
==REQ_Fx
)
4470 out_html("FreeBSD ");
4473 else if (request
==REQ_Nx
)
4474 out_html("NetBSD ");
4475 else if (request
==REQ_Ox
)
4476 out_html("OpenBSD ");
4477 else if (request
==REQ_Bx
)
4479 else if (request
==REQ_Ux
)
4481 else if (request
==REQ_Dx
)
4482 out_html("DragonFly ");
4484 c
=scan_troff_mandoc(c
,1,0);
4486 c
=scan_troff(c
,1,0);
4493 case REQ_Dl
: /* mdoc(7) */
4497 out_html("<BLOCKQUOTE>");
4499 c
=scan_troff_mandoc(c
, 1, NULL
);
4500 out_html("</BLOCKQUOTE>");
4507 case REQ_Bd
: /* mdoc(7) */
4508 { /* Seems like a kind of example/literal mode */
4509 char bd_options
[NULL_TERMINATED(MED_STR_MAX
)];
4510 char *nl
= strchr(c
,'\n');
4513 strlimitcpy(bd_options
, c
, nl
- c
, MED_STR_MAX
);
4515 mandoc_bd_options
= 0; /* Remember options for terminating Bl */
4516 if (strstr(bd_options
, "-offset indent"))
4518 mandoc_bd_options
|= BD_INDENT
;
4519 out_html("<BLOCKQUOTE>\n");
4521 if ( strstr(bd_options
, "-literal") || strstr(bd_options
, "-unfilled"))
4525 mandoc_bd_options
|= BD_LITERAL
;
4526 out_html(set_font("R"));
4527 out_html(change_to_size('0'));
4528 out_html("<PRE>\n");
4533 c
=skip_till_newline(c
);
4536 case REQ_Ed
: /* mdoc(7) */
4538 if (mandoc_bd_options
& BD_LITERAL
)
4542 out_html(set_font("R"));
4543 out_html(change_to_size('0'));
4544 out_html("</PRE>\n");
4547 if (mandoc_bd_options
& BD_INDENT
)
4548 out_html("</BLOCKQUOTE>\n");
4551 c
=skip_till_newline(c
);
4554 case REQ_Be
: /* mdoc(7) */
4558 out_html("<br><br>");
4564 c
=skip_till_newline(c
);
4567 case REQ_Xr
: /* mdoc(7) */ // ### FIXME: it should issue a <a href="man:somewhere(x)"> directly
4569 /* Translate xyz 1 to xyz(1)
4570 * Allow for multiple spaces. Allow the section to be missing.
4572 char buff
[NULL_TERMINATED(MED_STR_MAX
)];
4574 trans_char(c
,'"','\a');
4577 if (*c
== '\n') c
++; /* Skip spaces */
4578 while (isspace(*c
) && *c
!= '\n') c
++;
4579 while (isalnum(*c
) || *c
== '.' || *c
== ':' || *c
== '_' || *c
== '-')
4581 /* Copy the xyz part */
4584 if (bufptr
>= buff
+ MED_STR_MAX
) break;
4587 while (isspace(*c
) && *c
!= '\n') c
++; /* Skip spaces */
4590 /* Convert the number if there is one */
4593 if (bufptr
< buff
+ MED_STR_MAX
)
4599 if (bufptr
>= buff
+ MED_STR_MAX
) break;
4602 if (bufptr
< buff
+ MED_STR_MAX
)
4611 /* Copy the remainder */
4616 if (bufptr
>= buff
+ MED_STR_MAX
) break;
4622 scan_troff_mandoc(buff
, 1, NULL
);
4630 case REQ_Fl
: // mdoc(7) "FLags"
4632 trans_char(c
,'"','\a');
4634 sl
=fill_words(c
, wordlist
, &words
, true, &c
);
4635 out_html(set_font("B"));
4638 out_html("-"); // stdin or stdout
4642 for (i
=0;i
<words
;++i
)
4644 if (ispunct(wordlist
[i
][0]) && wordlist
[i
][0]!='-')
4646 scan_troff_mandoc(wordlist
[i
], 1, NULL
);
4651 out_html(" "); // Put a space between flags
4653 scan_troff_mandoc(wordlist
[i
], 1, NULL
);
4657 out_html(set_font("R"));
4665 case REQ_Pa
: /* mdoc(7) */
4666 case REQ_Pf
: /* mdoc(7) */
4668 trans_char(c
,'"','\a');
4671 c
=scan_troff_mandoc(c
, 1, NULL
);
4679 case REQ_Pp
: /* mdoc(7) */
4682 out_html("<br><br>\n");
4688 c
=skip_till_newline(c
);
4691 case REQ_Aq
: // mdoc(7) "Angle bracket Quote"
4692 c
=process_quote(c
,j
,"<",">");
4694 case REQ_Bq
: // mdoc(7) "Bracket Quote"
4695 c
=process_quote(c
,j
,"[","]");
4697 case REQ_Dq
: // mdoc(7) "Double Quote"
4698 c
=process_quote(c
,j
,"“","”");
4700 case REQ_Pq
: // mdoc(7) "Parenthese Quote"
4701 c
=process_quote(c
,j
,"(",")");
4703 case REQ_Qq
: // mdoc(7) "straight double Quote"
4704 c
=process_quote(c
,j
,""",""");
4706 case REQ_Sq
: // mdoc(7) "Single Quote"
4707 c
=process_quote(c
,j
,"‘","’");
4709 case REQ_Op
: /* mdoc(7) */
4711 trans_char(c
,'"','\a');
4714 out_html(set_font("R"));
4716 c
=scan_troff_mandoc(c
, 1, NULL
);
4717 out_html(set_font("R"));
4726 case REQ_Oo
: /* mdoc(7) */
4728 trans_char(c
,'"','\a');
4731 out_html(set_font("R"));
4733 c
=scan_troff_mandoc(c
, 1, NULL
);
4740 case REQ_Oc
: /* mdoc(7) */
4742 trans_char(c
,'"','\a');
4744 c
=scan_troff_mandoc(c
, 1, NULL
);
4745 out_html(set_font("R"));
4753 case REQ_Ql
: /* mdoc(7) */
4755 /* Single quote first word in the line */
4757 trans_char(c
,'"','\a');
4763 /* Find first whitespace after the
4764 * first word that isn't a mandoc macro
4766 while (*sp
&& isspace(*sp
)) sp
++;
4767 while (*sp
&& !isspace(*sp
)) sp
++;
4768 } while (*sp
&& isupper(*(sp
-2)) && islower(*(sp
-1)));
4770 /* Use a newline to mark the end of text to
4773 if (*sp
) *sp
= '\n';
4774 out_html("`"); /* Quote the text */
4775 c
=scan_troff_mandoc(c
, 1, NULL
);
4784 case REQ_Ar
: /* mdoc(7) */
4786 /* parse one line in italics */
4787 out_html(set_font("I"));
4788 trans_char(c
,'"','\a');
4792 /* An empty Ar means "file ..." */
4793 out_html("file ...");
4796 c
=scan_troff_mandoc(c
, 1, NULL
);
4797 out_html(set_font("R"));
4805 case REQ_Em
: /* mdoc(7) */
4808 trans_char(c
,'"','\a');
4811 c
=scan_troff_mandoc(c
, 1, NULL
);
4820 case REQ_Ad
: /* mdoc(7) */
4821 case REQ_Va
: /* mdoc(7) */
4822 case REQ_Xc
: /* mdoc(7) */
4824 /* parse one line in italics */
4825 out_html(set_font("I"));
4826 trans_char(c
,'"','\a');
4829 c
=scan_troff_mandoc(c
, 1, NULL
);
4830 out_html(set_font("R"));
4838 case REQ_Nd
: /* mdoc(7) */
4840 trans_char(c
,'"','\a');
4844 c
=scan_troff_mandoc(c
, 1, NULL
);
4852 case REQ_Nm
: // mdoc(7) "Name Macro" ### FIXME
4854 static char mandoc_name
[NULL_TERMINATED(SMALL_STR_MAX
)] = ""; // ### TODO Use QByteArray
4855 trans_char(c
,'"','\a');
4858 if (mandoc_synopsis
&& mandoc_name_count
)
4860 /* Break lines only in the Synopsis.
4861 * The Synopsis section seems to be treated
4862 * as a special case - Bummer!
4866 else if (!mandoc_name_count
)
4868 const char *nextbreak
= strchr(c
, '\n');
4869 const char *nextspace
= strchr(c
, ' ');
4870 if (nextspace
< nextbreak
)
4871 nextbreak
= nextspace
;
4875 /* Remember the name for later. */
4876 strlimitcpy(mandoc_name
, c
, nextbreak
- c
, SMALL_STR_MAX
);
4879 mandoc_name_count
++;
4881 out_html(set_font("B"));
4882 // ### FIXME: fill_words must be used
4883 while (*c
== ' '|| *c
== '\t') c
++;
4884 if ((tolower(*c
) >= 'a' && tolower(*c
) <= 'z' ) || (*c
>= '0' && *c
<= '9'))
4886 // alphanumeric argument
4887 c
=scan_troff_mandoc(c
, 1, NULL
);
4888 out_html(set_font("R"));
4893 /* If Nm has no argument, use one from an earlier
4894 * Nm command that did have one. Hope there aren't
4895 * too many commands that do this.
4897 out_html(mandoc_name
);
4898 out_html(set_font("R"));
4907 case REQ_Cd
: /* mdoc(7) */
4908 case REQ_Cm
: /* mdoc(7) */
4909 case REQ_Ic
: /* mdoc(7) */
4910 case REQ_Ms
: /* mdoc(7) */
4911 case REQ_Or
: /* mdoc(7) */
4912 case REQ_Sy
: /* mdoc(7) */
4914 /* parse one line in bold */
4915 out_html(set_font("B"));
4916 trans_char(c
,'"','\a');
4919 c
=scan_troff_mandoc(c
, 1, NULL
);
4920 out_html(set_font("R"));
4928 // ### FIXME: punctuation is handled badly!
4929 case REQ_Dv
: /* mdoc(7) */
4930 case REQ_Ev
: /* mdoc(7) */
4931 case REQ_Fr
: /* mdoc(7) */
4932 case REQ_Li
: /* mdoc(7) */
4933 case REQ_No
: /* mdoc(7) */
4934 case REQ_Ns
: /* mdoc(7) */
4935 case REQ_Tn
: /* mdoc(7) */
4936 case REQ_nN
: /* mdoc(7) */
4938 trans_char(c
,'"','\a');
4941 out_html(set_font("B"));
4942 c
=scan_troff_mandoc(c
, 1, NULL
);
4943 out_html(set_font("R"));
4951 case REQ_perc_A
: /* mdoc(7) biblio stuff */
4961 c
=scan_troff(c
, 1, NULL
); /* Don't allow embedded mandoc coms */
4974 out_html(set_font("I"));
4976 c
=scan_troff(c
, 1, NULL
); /* Don't allow embedded mandoc coms */
4977 out_html(set_font("R"));
4984 case REQ_UR
: // ### FIXME man(7) "URl"
4989 h
=fill_words(c
, wordlist
, &words
, false, &newc
);
4994 // A parameter : means that we do not want an URL, not here and not until .UE
4995 ur_ignore
=(!qstrcmp(h
,":"));
4999 // We cannot find the URL, assume :
5003 if (!ur_ignore
&& words
>0)
5005 out_html("<a href=\"");
5009 c
=newc
; // Go to next line
5012 case REQ_UE
: // ### FIXME man(7) "Url End"
5015 c
= skip_till_newline(c
);
5024 case REQ_UN
: // ### FIXME man(7) "Url Named anchor"
5028 h
=fill_words(c
, wordlist
, &words
, false, &newc
);
5033 out_html("<a name=\">");
5035 out_html("\" id=\"");
5037 out_html("\"></a>");
5042 case REQ_nroff
: // groff(7) "NROFF mode"
5044 case REQ_troff
: // groff(7) "TROFF mode"
5048 c
= skip_till_newline(c
);
5050 case REQ_als
: // groff(7) "ALias String"
5053 * Note an alias is supposed to be something like a hard link
5054 * However to make it simplier, we only copy the string.
5056 // Be careful: unlike .rn, the destination is first, origin is second
5057 kDebug(7107) << "start .als";
5059 const QByteArray
name ( scan_identifier( c
) );
5060 if ( name
.isEmpty() )
5062 kDebug(7107) << "EXCEPTION: empty destination string to alias";
5065 while (*c
&& isspace(*c
) && *c
!='\n') ++c
;
5066 const QByteArray
name2 ( scan_identifier ( c
) );
5067 if ( name2
.isEmpty() )
5069 kDebug(7107) << "EXCEPTION: empty origin string to alias";
5072 kDebug(7107) << "Alias " << BYTEARRAY( name2
) << " to " << BYTEARRAY( name
);
5073 c
=skip_till_newline(c
);
5074 if ( name
== name2
)
5076 kDebug(7107) << "EXCEPTION: same origin and destination string to alias: " << BYTEARRAY( name
);
5079 // Second parameter is origin (unlike in .rn)
5080 QMap
<QByteArray
,StringDefinition
>::iterator it
=s_stringDefinitionMap
.find(name2
);
5081 if (it
==s_stringDefinitionMap
.end())
5083 kDebug(7107) << "EXCEPTION: cannot find string to make alias of " << BYTEARRAY( name2
);
5087 StringDefinition def
=(*it
);
5088 s_stringDefinitionMap
.insert(name
,def
);
5090 kDebug(7107) << "end .als";
5093 case REQ_rr
: // groff(7) "Remove number Register"
5095 kDebug(7107) << "start .rr";
5097 const QByteArray
name ( scan_identifier( c
) );
5098 if ( name
.isEmpty() )
5100 kDebug(7107) << "EXCEPTION: empty origin string to remove/rename: ";
5103 c
= skip_till_newline( c
);
5104 QMap
<QByteArray
, NumberDefinition
>::iterator it
= s_numberDefinitionMap
.find( name
);
5105 if ( it
== s_numberDefinitionMap
.end() )
5107 kDebug(7107) << "EXCEPTION: trying to remove inexistant number register: ";
5111 s_numberDefinitionMap
.remove( name
);
5113 kDebug(7107) << "end .rr";
5116 case REQ_rnn
: // groff(7) "ReName Number register"
5118 kDebug(7107) << "start .rnn";
5120 const QByteArray
name ( scan_identifier ( c
) );
5121 if ( name
.isEmpty() )
5123 kDebug(7107) << "EXCEPTION: empty origin to remove/rename number register";
5126 while (*c
&& isspace(*c
) && *c
!='\n') ++c
;
5127 const QByteArray
name2 ( scan_identifier ( c
) );
5128 if ( name2
.isEmpty() )
5130 kDebug(7107) << "EXCEPTION: empty destination to rename number register";
5133 c
= skip_till_newline( c
);
5134 QMap
<QByteArray
,NumberDefinition
>::iterator it
=s_numberDefinitionMap
.find(name
);
5135 if (it
==s_numberDefinitionMap
.end())
5137 kDebug(7107) << "EXCEPTION: cannot find number register to rename" << BYTEARRAY( name
);
5141 NumberDefinition def
=(*it
);
5142 s_numberDefinitionMap
.remove(name
); // ### QT4: removeAll
5143 s_numberDefinitionMap
.insert(name2
,def
);
5145 kDebug(7107) << "end .rnn";
5148 case REQ_aln
: // groff(7) "ALias Number Register"
5151 * Note an alias is supposed to be something like a hard link
5152 * However to make it simplier, we only copy the string.
5154 // Be careful: unlike .rnn, the destination is first, origin is second
5155 kDebug(7107) << "start .aln";
5157 const QByteArray
name ( scan_identifier( c
) );
5158 if ( name
.isEmpty() )
5160 kDebug(7107) << "EXCEPTION: empty destination number register to alias";
5163 while (*c
&& isspace(*c
) && *c
!='\n') ++c
;
5164 const QByteArray
name2 ( scan_identifier( c
) );
5165 if ( name2
.isEmpty() )
5167 kDebug(7107) << "EXCEPTION: empty origin number register to alias";
5170 kDebug(7107) << "Alias " << BYTEARRAY( name2
) << " to " << BYTEARRAY( name
);
5171 c
= skip_till_newline( c
);
5172 if ( name
== name2
)
5174 kDebug(7107) << "EXCEPTION: same origin and destination number register to alias: " << BYTEARRAY( name
);
5177 // Second parameter is origin (unlike in .rnn)
5178 QMap
<QByteArray
,NumberDefinition
>::iterator it
=s_numberDefinitionMap
.find(name2
);
5179 if (it
==s_numberDefinitionMap
.end())
5181 kDebug(7107) << "EXCEPTION: cannot find string to make alias: " << BYTEARRAY( name2
);
5185 NumberDefinition def
=(*it
);
5186 s_numberDefinitionMap
.insert(name
,def
);
5188 kDebug(7107) << "end .aln";
5191 case REQ_shift
: // groff(7) "SHIFT parameter"
5195 while (*h
&& *h
!='\n' && isdigit(*h
) ) ++h
;
5196 const char tempchar
= *h
;
5198 const QByteArray
number( c
);
5200 c
= skip_till_newline( h
);
5201 unsigned int result
= 1; // Numbers of shifts to do
5202 if ( !number
.isEmpty() )
5205 result
= number
.toUInt(&ok
);
5206 if ( !ok
|| result
< 1 )
5209 for ( unsigned int num
= 0; num
< result
; ++num
)
5211 if ( !s_argumentList
.isEmpty() )
5212 s_argumentList
.pop_front();
5216 case REQ_while
: // groff(7) "WHILE loop"
5218 request_while( c
, j
, mandoc_command
);
5221 case REQ_do
: // groff(7) "DO command"
5223 // ### HACK: we just replace do by a \n and a .
5227 // The . will be treated as next character
5232 if (mandoc_command
&&
5233 ((isupper(*c
) && islower(*(c
+1)))
5234 || (islower(*c
) && isupper(*(c
+1)))) )
5236 /* Let through any mdoc(7) commands that haven't
5238 * I don't want to miss anything out of the text.
5240 char buf
[4] = { c
[0], c
[1], ' ', 0 };
5241 out_html(buf
); /* Print the command (it might just be text). */
5243 trans_char(c
,'"','\a');
5245 out_html(set_font("R"));
5246 c
=scan_troff(c
, 1, NULL
);
5254 c
=skip_till_newline(c
);
5268 static int contained_tab
=0;
5269 static bool mandoc_line
=false; /* Signals whether to look for embedded mandoc
5273 static char *scan_troff(char *c
, bool san
, char **result
)
5274 { /* san : stop at newline */
5276 char intbuff
[NULL_TERMINATED(MED_STR_MAX
)];
5278 #define FLUSHIBP if (ibp) { intbuff[ibp]=0; out_html(intbuff); ibp=0; }
5280 int exbuffpos
, exbuffmax
, exnewline_for_fun
;
5287 exnewline_for_fun
=newline_for_fun
;
5288 exscaninbuff
=scaninbuff
;
5293 buffpos
=qstrlen(buffer
);
5296 buffer
= stralloc(LARGE_STR_MAX
);
5298 buffmax
=LARGE_STR_MAX
;
5302 h
=c
; // ### FIXME below are too many tests that may go before the position of c
5303 /* start scanning */
5305 // ### VERIFY: a dot must be at first position, we cannot add newlines or it would allow spaces before a dot
5315 while (h
&& *h
&& (!san
|| newline_for_fun
|| *h
!='\n')) {
5317 if (*h
==escapesym
) {
5321 } else if (*h
==controlsym
&& h
[-1]=='\n') {
5324 h
= scan_request(h
);
5325 if (h
&& san
&& h
[-1]=='\n') h
--;
5326 } else if (mandoc_line
5327 && ((*(h
-1)) && (isspace(*(h
-1)) || (*(h
-1))=='\n'))
5328 && *(h
) && isupper(*(h
))
5329 && *(h
+1) && islower(*(h
+1))
5330 && *(h
+2) && isspace(*(h
+2))) {
5331 // mdoc(7) embedded command eg ".It Fl Ar arg1 Fl Ar arg2"
5333 h
= scan_request(h
);
5334 if (san
&& h
[-1]=='\n') h
--;
5335 } else if (*h
==nobreaksym
&& h
[-1]=='\n') {
5338 h
= scan_request(h
);
5339 if (san
&& h
[-1]=='\n') h
--;
5342 if (still_dd
&& isalnum(*h
) && h
[-1]=='\n') {
5343 /* sometimes a .HP request is not followed by a .br request */
5382 if (h
!= c
&& h
[-1]=='\n' && fillout
) {
5387 if (contained_tab
&& fillout
) {
5396 intbuff
[ibp
++]='\n';
5403 /* like a typewriter, not like TeX */
5404 tabstops
[19]=curpos
+1;
5405 while (curtab
<maxtstop
&& tabstops
[curtab
]<=curpos
)
5407 if (curtab
<maxtstop
) {
5409 while (curpos
<tabstops
[curtab
]) {
5411 if (ibp
>480) { FLUSHIBP
; }
5416 while (curpos
<tabstops
[curtab
]) {
5426 if (*h
==' ' && (h
[-1]=='\n' || usenbsp
)) {
5428 if (!usenbsp
&& fillout
) {
5433 if (usenbsp
) out_html(" "); else intbuff
[ibp
++]=' ';
5434 } else if (*h
>31 && *h
<127) intbuff
[ibp
++]=*h
;
5435 else if (((unsigned char)(*h
))>127) {
5441 if (ibp
> (MED_STR_MAX
- 20)) FLUSHIBP
;
5446 if (buffer
) buffer
[buffpos
]='\0';
5447 if (san
&& h
&& *h
) h
++;
5448 newline_for_fun
=exnewline_for_fun
;
5454 scaninbuff
=exscaninbuff
;
5461 static char *scan_troff_mandoc(char *c
, bool san
, char **result
)
5465 bool oldval
= mandoc_line
;
5467 while (*end
&& *end
!= '\n') {
5472 && ispunct(*(end
- 1))
5473 && isspace(*(end
- 2)) && *(end
- 2) != '\n') {
5474 /* Don't format lonely punctuation E.g. in "xyz ," format
5475 * the xyz and then append the comma removing the space.
5478 ret
= scan_troff(c
, san
, result
);
5479 *(end
- 2) = *(end
- 1);
5483 ret
= scan_troff(c
, san
, result
);
5485 mandoc_line
= oldval
;
5490 void scan_man_page(const char *man_page
)
5495 kDebug(7107) << "Start scanning man page";
5498 // Unlike man2html, we actually call this several times, hence the need to
5499 // properly cleanup all those static vars
5500 s_ifelseval
.clear();
5502 s_characterDefinitionMap
.clear();
5503 InitCharacterDefinitions();
5505 s_stringDefinitionMap
.clear();
5506 InitStringDefinitions();
5508 s_numberDefinitionMap
.clear();
5509 InitNumberDefinitions();
5511 s_argumentList
.clear();
5515 s_dollarZero
= ""; // No macro called yet!
5517 output_possible
= false;
5518 int strLength
= qstrlen(man_page
);
5519 char *buf
= new char[strLength
+ 2];
5520 qstrcpy(buf
+1, man_page
);
5523 kDebug(7107) << "Parse man page";
5525 scan_troff(buf
+1,0,NULL
);
5527 kDebug(7107) << "Man page parsed!";
5529 while (itemdepth
|| dl_set
[itemdepth
]) {
5530 out_html("</DL>\n");
5531 if (dl_set
[itemdepth
]) dl_set
[itemdepth
]=0;
5532 else if (itemdepth
> 0) itemdepth
--;
5535 out_html(set_font("R"));
5536 out_html(change_to_size(0));
5544 output_real("<div style=\"margin-left: 2cm\">\n");
5548 if (output_possible
) {
5549 output_real("</div>\n");
5550 output_real("<div class=\"bannerBottom\" style=\"background-image: url(");
5551 output_real(cssPath
);
5552 output_real("/bottom-middle.png); background-repeat: x-repeat; width: 100%; height: 100px; bottom:0pt;\">\n");
5553 output_real("<div class=\"bannerBottomLeft\">\n");
5554 output_real("<img src=\"");
5555 output_real(cssPath
);
5556 output_real("/bottom-left.png\" style=\"margin: 0pt;\" alt=\"Bottom left of the banner\">\n");
5557 output_real("</div>\n");
5558 output_real("<div class=\"bannerBottomRight\">\n");
5559 output_real("<img src=\"");
5560 output_real(cssPath
);
5561 output_real("/bottom-right.png\" style=\"margin: 0pt\" alt=\"Bottom right of the banner\">\n");
5562 output_real("</div>\n");
5563 output_real("</div>\n");
5565 output_real("</BODY>\n</HTML>\n");
5570 s_characterDefinitionMap
.clear();
5571 s_stringDefinitionMap
.clear();
5572 s_numberDefinitionMap
.clear();
5573 s_argumentList
.clear();
5575 // reinit static variables for reuse
5589 for (int i
= 0; i
< 20; i
++)
5592 for (int i
= 0; i
< 12; i
++)
5593 tabstops
[i
] = (i
+1)*8;
5597 mandoc_name_count
= 0;
5600 #ifdef SIMPLE_MAN2HTML
5601 void output_real(const char *insert
)
5606 char *read_man_page(const char *filename
)
5609 char *man_buf
= NULL
;
5611 FILE *man_stream
= NULL
;
5614 if (stat(filename
, &stbuf
) == -1) {
5615 std::cerr
<< "read_man_page: can not find " << filename
<< endl
;
5618 if (!S_ISREG(stbuf
.st_mode
)) {
5619 std::cerr
<< "read_man_page: no file " << filename
<< endl
;
5622 buf_size
= stbuf
.st_size
;
5623 man_buf
= stralloc(buf_size
+5);
5625 man_stream
= fopen(filename
, "r");
5628 if (fread(man_buf
+1, 1, buf_size
, man_stream
) == buf_size
) {
5629 man_buf
[buf_size
] = '\n';
5630 man_buf
[buf_size
+ 1] = man_buf
[buf_size
+ 2] = '\0';
5640 #ifndef KIO_MAN_TEST
5641 int main(int argc
, char **argv
)
5646 std::cerr
<< "call: " << argv
[0] << " <filename>\n";
5649 if (chdir(argv
[1])) {
5650 char *buf
= read_man_page(argv
[1]);
5656 DIR *dir
= opendir(".");
5658 while ((ent
= readdir(dir
)) != NULL
) {
5659 cerr
<< "converting " << ent
->d_name
<< endl
;
5660 char *buf
= read_man_page(ent
->d_name
);
5675 // kate: space-indent on; indent-width 4; replace-tabs on;