/* This file is part of the KDE libraries Copyright (C) 2005 Nicolas GOUTTE <goutte@kde.org> ### TODO: who else? */ // Start of verbatim comment /* ** This program was written by Richard Verhoeven (NL:5482ZX35) ** at the Eindhoven University of Technology. Email: rcb5@win.tue.nl ** ** Permission is granted to distribute, modify and use this program as long ** as this comment is not removed or changed. */ // End of verbatim comment // kate: space-indent on; indent-width 4; replace-tabs on; /* * man2html-linux-1.0/1.1 * This version modified for Redhat/Caldera linux - March 1996. * Michael Hamilton <michael@actrix.gen.nz>. * * man2html-linux-1.2 * Added support for BSD mandoc pages - I didn't have any documentation * on the mandoc macros, so I may have missed some. * Michael Hamilton <michael@actrix.gen.nz>. * * vh-man2html-1.3 * Renamed to avoid confusion (V for Verhoeven, H for Hamilton). * * vh-man2html-1.4 * Now uses /etc/man.config * Added support for compressed pages. * Added "length-safe" string operations for client input parameters. * More secure, -M secured, and client input string lengths checked. * */ /* ** If you want to use this program for your WWW server, adjust the line ** which defines the CGIBASE or compile it with the -DCGIBASE='"..."' option. ** ** You have to adjust the built-in manpath to your local system. Note that ** every directory should start and end with the '/' and that the first ** directory should be "/" to allow a full path as an argument. ** ** The program first check if PATH_INFO contains some information. ** If it does (t.i. man2html/some/thing is used), the program will look ** for a manpage called PATH_INFO in the manpath. ** ** Otherwise the manpath is searched for the specified command line argument, ** where the following options can be used: ** ** name name of manpage (csh, printf, xv, troff) ** section the section (1 2 3 4 5 6 7 8 9 n l 1v ...) ** -M path an extra directory to look for manpages (replaces "/") ** ** If man2html finds multiple manpages that satisfy the options, an index ** is displayed and the user can make a choice. If only one page is ** found, that page will be displayed. ** ** man2html will add links to the converted manpages. The function add_links ** is used for that. At the moment it will add links as follows, where ** indicates what should match to start with: ** ^^^ ** Recognition Item Link ** ---------------------------------------------------------- ** name(*) Manpage ../man?/name.* ** ^ ** name@hostname Email address mailto:name@hostname ** ^ ** method://string URL method://string ** ^^^ ** www.host.name WWW server http://www.host.name ** ^^^^ ** ftp.host.name FTP server ftp://ftp.host.name ** ^^^^ ** <file.h> Include file file:/usr/include/file.h ** ^^^ ** ** Since man2html does not check if manpages, hosts or email addresses exist, ** some links might not work. For manpages, some extra checks are performed ** to make sure not every () pair creates a link. Also out of date pages ** might point to incorrect places. ** ** The program will not allow users to get system specific files, such as ** /etc/passwd. It will check that "man" is part of the specified file and ** that "/../" isn't. Even if someone manages to get such file, man2html will ** handle it like a manpage and will usually not produce any output (or crash). ** ** If you find any bugs when normal manpages are converted, please report ** them to me (rcb5@win.tue.nl) after you have checked that man(1) can handle ** the manpage correct. ** ** Known bugs and missing features: ** ** * Equations are not converted at all. ** * Tables are converted but some features are not possible in html. ** * The tabbing environment is converted by counting characters and adding ** spaces. This might go wrong (outside <PRE>) ** * Some manpages rely on the fact that troff/nroff is used to convert ** them and use features which are not descripted in the man manpages. ** (definitions, calculations, conditionals, requests). I can't guarantee ** that all these features work on all manpages. (I didn't have the ** time to look through all the available manpages.) */ #ifdef SIMPLE_MAN2HTML // We suppose that we run on a standard Linux # define HAVE_STRING_H 1 # define HAVE_UNISTD_H 1 #else # include <config.h> #endif #include <ctype.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #ifdef HAVE_STRING_H # include <string.h> #endif #include <stdio.h> #include <tqvaluestack.h> #include <tqstring.h> #include <tqptrlist.h> #include <tqmap.h> #include <tqdatetime.h> #ifdef SIMPLE_MAN2HTML # include <stdlib.h> # include <iostream> # include <dirent.h> # include <sys/stat.h> # define kdDebug(x) cerr # define kdWarning(x) cerr << "WARNING " #else # include <tqtextcodec.h> # include <kdebug.h> # include <tdeversion.h> #endif #include "man2html.h" using namespace std; #define NULL_TERMINATED(n) ((n) + 1) #define HUGE_STR_MAX 10000 #define LARGE_STR_MAX 2000 #define MED_STR_MAX 500 #define SMALL_STR_MAX 100 #define TINY_STR_MAX 10 #if 1 // The output is current too horrible to be called HTML 4.01 #define DOCTYPE "<!DOCTYPE HTML>" #else #define DOCTYPE "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" #endif /* mdoc(7) Bl/El lists to HTML list types */ #define BL_DESC_LIST 1 #define BL_BULLET_LIST 2 #define BL_ENUM_LIST 4 /* mdoc(7) Bd/Ed example(?) blocks */ #define BD_LITERAL 1 #define BD_INDENT 2 static int s_nroff = 1; // NROFF mode by default static int mandoc_name_count = 0; /* Don't break on the first Nm */ static char *stralloc(int len) { /* allocate enough for len + NULL */ char *news = new char [len+1]; #ifdef SIMPLE_MAN2HTML if (!news) { cerr << "man2html: out of memory" << endl; exit(EXIT_FAILURE); } #else // modern compilers do not return a NULL pointer for a new #endif return news; } static char *strlimitcpy(char *to, char *from, int n, int limit) { /* Assumes space for limit plus a null */ const int len = n > limit ? limit : n; tqstrncpy(to, from, len + 1); to[len] = '\0'; return to; } /* below this you should not change anything unless you know a lot ** about this program or about troff. */ /// Structure for character definitions struct CSTRDEF { int nr, slen; const char *st; }; const char NEWLINE[2]="\n"; /** * Class for defining strings and macros */ class StringDefinition { public: StringDefinition( void ) : m_length(0) {} StringDefinition( int len, const char* cstr ) : m_length( len ), m_output( cstr ) {} public: int m_length; ///< Length of output text TQCString m_output; ///< Defined string }; /** * Class for defining number registers * \note Not for internal read-only registers */ class NumberDefinition { public: NumberDefinition( void ) : m_value(0), m_increment(0) {} NumberDefinition( int value ) : m_value( value ), m_increment(0) {} NumberDefinition( int value, int incr) : m_value( value ), m_increment( incr ) {} public: int m_value; ///< value of number register int m_increment; ///< Increment of number register // ### TODO: display form (.af) }; /** * Map of character definitions */ static TQMap<TQCString,StringDefinition> s_characterDefinitionMap; /** * Map of string variable and macro definitions * \note String variables and macros are the same thing! */ static TQMap<TQCString,StringDefinition> s_stringDefinitionMap; /** * Map of number registers * \note Intern number registers (starting with a dot are not handled here) */ static TQMap<TQCString,NumberDefinition> s_numberDefinitionMap; static void fill_old_character_definitions( void ); /** * Initialize character variables */ static void InitCharacterDefinitions( void ) { fill_old_character_definitions(); // ### HACK: as we are converting to HTML too early, define characters with HTML references s_characterDefinitionMap.insert( "<-", StringDefinition( 1, "←" ) ); // <- s_characterDefinitionMap.insert( "->", StringDefinition( 1, "→" ) ); // -> s_characterDefinitionMap.insert( "<>", StringDefinition( 1, "↔" ) ); // <> s_characterDefinitionMap.insert( "<=", StringDefinition( 1, "≤" ) ); // <= s_characterDefinitionMap.insert( ">=", StringDefinition( 1, "≥" ) ); // >= // End HACK } /** * Initialize string variables */ static void InitStringDefinitions( void ) { // mdoc-only, see mdoc.samples(7) s_stringDefinitionMap.insert( "<=", StringDefinition( 1, "≤" ) ); s_stringDefinitionMap.insert( ">=", StringDefinition( 1, "≥" ) ); s_stringDefinitionMap.insert( "Rq", StringDefinition( 1, "”" ) ); s_stringDefinitionMap.insert( "Lq", StringDefinition( 1, "“" ) ); s_stringDefinitionMap.insert( "ua", StringDefinition( 1, "&circ" ) ); // Note this is different from \(ua s_stringDefinitionMap.insert( "aa", StringDefinition( 1, "´" ) ); s_stringDefinitionMap.insert( "ga", StringDefinition( 1, "`" ) ); s_stringDefinitionMap.insert( "q", StringDefinition( 1, """ ) ); s_stringDefinitionMap.insert( "Pi", StringDefinition( 1, "π" ) ); s_stringDefinitionMap.insert( "Ne", StringDefinition( 1, "≠" ) ); s_stringDefinitionMap.insert( "Le", StringDefinition( 1, "≤" ) ); s_stringDefinitionMap.insert( "Ge", StringDefinition( 1, "≥" ) ); s_stringDefinitionMap.insert( "Lt", StringDefinition( 1, "<" ) ); s_stringDefinitionMap.insert( "Gt", StringDefinition( 1, ">" ) ); s_stringDefinitionMap.insert( "Pm", StringDefinition( 1, "±" ) ); s_stringDefinitionMap.insert( "If", StringDefinition( 1, "∞" ) ); s_stringDefinitionMap.insert( "Na", StringDefinition( 3, "NaN" ) ); s_stringDefinitionMap.insert( "Ba", StringDefinition( 1, "|" ) ); // end mdoc-only // man(7) s_stringDefinitionMap.insert( "Tm", StringDefinition( 1, "™" ) ); // \*(TM s_stringDefinitionMap.insert( "R", StringDefinition( 1, "®" ) ); // \*R // end man(7) // Missing characters from man(7): // \*S "Change to default font size" #ifndef SIMPLE_MAN2HTML // Special KDE KIO man: const TQCString tdeversion(TDE_VERSION_STRING); s_stringDefinitionMap.insert( ".TDE_VERSION_STRING", StringDefinition( tdeversion.length(), tdeversion ) ); #endif } /** * Initialize number registers * \note Internal read-only registers are not handled here */ static void InitNumberDefinitions( void ) { // As the date number registers are more for end-users, better choose local time. // Groff seems to support Gregorian dates only TQDate today( TQDate::currentDate( Qt::LocalTime ) ); s_numberDefinitionMap.insert( "year", today.year() ); // Y2K-correct year s_numberDefinitionMap.insert( "yr", today.year() - 1900 ); // Y2K-incorrect year s_numberDefinitionMap.insert( "mo", today.month() ); s_numberDefinitionMap.insert( "dy", today.day() ); s_numberDefinitionMap.insert( "dw", today.dayOfWeek() ); } #define V(A,B) ((A)*256+(B)) //used in expand_char, e.g. for "\(bu" // see groff_char(7) for list static CSTRDEF standardchar[] = { { V('*','*'), 1, "*" }, { V('*','A'), 1, "Α" }, { V('*','B'), 1, "Β" }, { V('*','C'), 1, "Ξ" }, { V('*','D'), 1, "Δ" }, { V('*','E'), 1, "Ε" }, { V('*','F'), 1, "Φ" }, { V('*','G'), 1, "Γ" }, { V('*','H'), 1, "Θ" }, { V('*','I'), 1, "Ι" }, { V('*','K'), 1, "Κ" }, { V('*','L'), 1, "Λ" }, { V('*','M'), 1, "&Mu:" }, { V('*','N'), 1, "Ν" }, { V('*','O'), 1, "Ο" }, { V('*','P'), 1, "Π" }, { V('*','Q'), 1, "Ψ" }, { V('*','R'), 1, "Ρ" }, { V('*','S'), 1, "Σ" }, { V('*','T'), 1, "Τ" }, { V('*','U'), 1, "Υ" }, { V('*','W'), 1, "Ω" }, { V('*','X'), 1, "Χ" }, { V('*','Y'), 1, "Η" }, { V('*','Z'), 1, "Ζ" }, { V('*','a'), 1, "α"}, { V('*','b'), 1, "β"}, { V('*','c'), 1, "ξ"}, { V('*','d'), 1, "δ"}, { V('*','e'), 1, "ε"}, { V('*','f'), 1, "φ"}, { V('*','g'), 1, "γ"}, { V('*','h'), 1, "θ"}, { V('*','i'), 1, "ι"}, { V('*','k'), 1, "κ"}, { V('*','l'), 1, "λ"}, { V('*','m'), 1, "μ" }, { V('*','n'), 1, "ν"}, { V('*','o'), 1, "ο"}, { V('*','p'), 1, "π"}, { V('*','q'), 1, "ψ"}, { V('*','r'), 1, "ρ"}, { V('*','s'), 1, "σ"}, { V('*','t'), 1, "τ"}, { V('*','u'), 1, "υ"}, { V('*','w'), 1, "ω"}, { V('*','x'), 1, "χ"}, { V('*','y'), 1, "η"}, { V('*','z'), 1, "ζ"}, { V('+','-'), 1, "±" }, // not in groff_char(7) { V('+','f'), 1, "φ"}, // phi1, we use the standard phi { V('+','h'), 1, "θ"}, // theta1, we use the standard theta { V('+','p'), 1, "ω"}, // omega1, we use the standard omega { V('1','2'), 1, "½" }, { V('1','4'), 1, "¼" }, { V('3','4'), 1, "¾" }, { V('F','i'), 1, "ffi" }, // ffi ligature { V('F','l'), 1, "ffl" }, // ffl ligature { V('a','p'), 1, "~" }, { V('b','r'), 1, "|" }, { V('b','u'), 1, "•" }, { V('b','v'), 1, "|" }, { V('c','i'), 1, "○" }, // circle ### TODO verify { V('c','o'), 1, "©" }, { V('c','t'), 1, "¢" }, { V('d','e'), 1, "°" }, { V('d','g'), 1, "†" }, { V('d','i'), 1, "÷" }, { V('e','m'), 1, "&emdash;" }, { V('e','n'), 1, "&endash;"}, { V('e','q'), 1, "=" }, { V('e','s'), 1, "∅" }, { V('f','f'), 1, "�xFB00;" }, // ff ligature { V('f','i'), 1, "�xFB01;" }, // fi ligature { V('f','l'), 1, "�xFB02;" }, // fl ligature { V('f','m'), 1, "′" }, { V('g','a'), 1, "`" }, { V('h','y'), 1, "-" }, { V('l','c'), 2, "|¯" }, // ### TODO: not in groff_char(7) { V('l','f'), 2, "|_" }, // ### TODO: not in groff_char(7) { V('l','k'), 1, "<FONT SIZE=+2>{</FONT>" }, // ### TODO: not in groff_char(7) { V('m','i'), 1, "-" }, // ### TODO: not in groff_char(7) { V('m','u'), 1, "×" }, { V('n','o'), 1, "¬" }, { V('o','r'), 1, "|" }, { V('p','l'), 1, "+" }, { V('r','c'), 2, "¯|" }, // ### TODO: not in groff_char(7) { V('r','f'), 2, "_|" }, // ### TODO: not in groff_char(7) { V('r','g'), 1, "®" }, { V('r','k'), 1, "<FONT SIZE=+2>}</FONT>" }, // ### TODO: not in groff_char(7) { V('r','n'), 1, "‾" }, { V('r','u'), 1, "_" }, { V('s','c'), 1, "§" }, { V('s','l'), 1, "/" }, { V('s','q'), 2, "□" }, // WHITE SQUARE { V('t','s'), 1, "ς" }, // FINAL SIGMA { V('u','l'), 1, "_" }, { V('-','D'), 1, "Ð" }, { V('S','d'), 1, "ð" }, { V('T','P'), 1, "Þ" }, { V('T','p'), 1, "þ" }, { V('A','E'), 1, "Æ" }, { V('a','e'), 1, "æ" }, { V('O','E'), 1, "Œ" }, { V('o','e'), 1, "œ" }, { V('s','s'), 1, "ß" }, { V('\'','A'), 1, "Á" }, { V('\'','E'), 1, "É" }, { V('\'','I'), 1, "Í" }, { V('\'','O'), 1, "Ó" }, { V('\'','U'), 1, "Ú" }, { V('\'','Y'), 1, "Ý" }, { V('\'','a'), 1, "á" }, { V('\'','e'), 1, "é" }, { V('\'','i'), 1, "í" }, { V('\'','o'), 1, "ó" }, { V('\'','u'), 1, "ú" }, { V('\'','y'), 1, "ý" }, { V(':','A'), 1, "Ä" }, { V(':','E'), 1, "Ë" }, { V(':','I'), 1, "Ï" }, { V(':','O'), 1, "Ö" }, { V(':','U'), 1, "Ü" }, { V(':','a'), 1, "ä" }, { V(':','e'), 1, "ë" }, { V(':','i'), 1, "ï" }, { V(':','o'), 1, "ö" }, { V(':','u'), 1, "ü" }, { V(':','y'), 1, "ÿ" }, { V('^','A'), 1, "Â" }, { V('^','E'), 1, "Ê" }, { V('^','I'), 1, "Î" }, { V('^','O'), 1, "Ô" }, { V('^','U'), 1, "Û" }, { V('^','a'), 1, "â" }, { V('^','e'), 1, "ê" }, { V('^','i'), 1, "î" }, { V('^','o'), 1, "ô" }, { V('^','u'), 1, "û" }, { V('`','A'), 1, "À" }, { V('`','E'), 1, "È" }, { V('`','I'), 1, "Ì" }, { V('`','O'), 1, "Ò" }, { V('`','U'), 1, "Ù" }, { V('`','a'), 1, "à" }, { V('`','e'), 1, "è" }, { V('`','i'), 1, "ì" }, { V('`','o'), 1, "ò" }, { V('`','u'), 1, "ù" }, { V('~','A'), 1, "Ã" }, { V('~','N'), 1, "Ñ" }, { V('~','O'), 1, "Õ" }, { V('~','a'), 1, "ã" }, { V('~','n'), 1, "&ntidle;" }, { V('~','o'), 1, "&otidle;" }, { V(',','C'), 1, "Ç" }, { V(',','c'), 1, "ç" }, { V('/','L'), 1, "Ł" }, { V('/','l'), 1, "ł" }, { V('/','O'), 1, "Ø" }, { V('/','o'), 1, "ø" }, { V('o','A'), 1, "Å" }, { V('o','a'), 1, "å" }, { V('a','"'), 1, "\"" }, { V('a','-'), 1, "¯" }, { V('a','.'), 1, "." }, { V('a','^'), 1, "ˆ" }, { V('a','a'), 1, "´" }, { V('a','b'), 1, "`" }, { V('a','c'), 1, "¸" }, { V('a','d'), 1, "¨" }, { V('a','h'), 1, "˂" }, // caron { V('a','o'), 1, "˚" }, // ring { V('a','~'), 1, "˜" }, { V('h','o'), 1, "˛" }, // ogonek { V('.','i'), 1, "ı" }, // dot less i { V('C','s'), 1, "¤" }, { V('D','o'), 1, "$" }, { V('P','o'), 1, "£" }, { V('Y','e'), 1, "¥" }, { V('F','n'), 1, "ƒ" }, { V('F','o'), 1, "«" }, { V('F','c'), 1, "»" }, { V('f','o'), 1, "‹" }, // single left guillemet { V('f','c'), 1, "›" }, // single right guillemet { V('r','!'), 1, "&iecl;" }, { V('r','?'), 1, "¿" }, { V('O','f'), 1, "ª" }, { V('O','m'), 1, "º" }, { V('p','c'), 1, "·" }, { V('S','1'), 1, "¹" }, { V('S','2'), 1, "²" }, { V('S','3'), 1, "³" }, { V('<','-'), 1, "←" }, { V('-','>'), 1, "→" }, { V('<','>'), 1, "↔" }, { V('d','a'), 1, "↓" }, { V('u','a'), 1, "↑" }, { V('l','A'), 1, "⇐" }, { V('r','A'), 1, "⇒" }, { V('h','A'), 1, "⇔" }, { V('d','A'), 1, "⇓" }, { V('u','A'), 1, "⇑" }, { V('b','a'), 1, "|" }, { V('b','b'), 1, "¦" }, { V('t','m'), 1, "™" }, { V('d','d'), 1, "‡" }, { V('p','s'), 1, "¶" }, { V('%','0'), 1, "‰" }, { V('f','/'), 1, "⁄" }, // Fraction slash { V('s','d'), 1, "″" }, { V('h','a'), 1, "^" }, { V('t','i'), 1, "&tidle;" }, { V('l','B'), 1, "[" }, { V('r','B'), 1, "]" }, { V('l','C'), 1, "{" }, { V('r','C'), 1, "}" }, { V('l','a'), 1, "<" }, { V('r','a'), 1, ">" }, { V('l','h'), 1, "≤" }, { V('r','h'), 1, "≥" }, { V('B','q'), 1, "„" }, { V('b','q'), 1, "‚" }, { V('l','q'), 1, "“" }, { V('r','q'), 1, "”" }, { V('o','q'), 1, "‘" }, { V('c','q'), 1, "’" }, { V('a','q'), 1, "'" }, { V('d','q'), 1, "\"" }, { V('a','t'), 1, "@" }, { V('s','h'), 1, "#" }, { V('r','s'), 1, "\\" }, { V('t','f'), 1, "∴" }, { V('~','~'), 1, "≅" }, { V('~','='), 1, "≈" }, { V('!','='), 1, "≠" }, { V('<','='), 1, "≤" }, { V('=','='), 1, "≡" }, { V('=','~'), 1, "≅" }, // ### TODO: verify { V('>','='), 1, "≥" }, { V('A','N'), 1, "∧" }, { V('O','R'), 1, "∨" }, { V('t','e'), 1, "∃" }, { V('f','a'), 1, "∀" }, { V('A','h'), 1, "ℵ" }, { V('I','m'), 1, "ℑ" }, { V('R','e'), 1, "ℜ" }, { V('i','f'), 1, "∞" }, { V('m','d'), 1, "⋅" }, { V('m','o'), 1, "∆" }, // element ### TODO verify { V('n','m'), 1, "∉" }, { V('p','t'), 1, "∝" }, { V('p','p'), 1, "⊥" }, { V('s','b'), 1, "⊂" }, { V('s','p'), 1, "⊃" }, { V('i','b'), 1, "⊆" }, { V('i','p'), 1, "⊇" }, { V('i','s'), 1, "∫" }, { V('s','r'), 1, "√" }, { V('p','d'), 1, "∂" }, { V('c','*'), 1, "⊗" }, { V('c','+'), 1, "⊕" }, { V('c','a'), 1, "∩" }, { V('c','u'), 1, "∪" }, { V('g','r'), 1, "V" }, // gradient ### TODO Where in Unicode? { V('C','R'), 1, "↵" }, { V('s','t'), 2, "-)" }, // "such that" ### TODO Where in Unicode? { V('/','_'), 1, "∠" }, { V('w','p'), 1, "℘" }, { V('l','z'), 1, "◊" }, { V('a','n'), 1, "-" }, // "horizontal arrow extension" ### TODO Where in Unicode? }; /* default: print code */ /* static char eqndelimopen=0, eqndelimclose=0; */ static char escapesym='\\', nobreaksym='\'', controlsym='.', fieldsym=0, padsym=0; static char *buffer=NULL; static int buffpos=0, buffmax=0; static bool scaninbuff=false; static int itemdepth=0; static int section=0; static int dl_set[20]= { 0 }; static bool still_dd=0; static int tabstops[20] = { 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96 }; static int maxtstop=12; static int curpos=0; static char *scan_troff(char *c, bool san, char **result); static char *scan_troff_mandoc(char *c, bool san, char **result); static TQValueList<char*> s_argumentList; static TQCString htmlPath, cssPath; static TQCString s_dollarZero; // Value of $0 void setResourcePath(const TQCString& _htmlPath, const TQCString& _cssPath) { htmlPath=_htmlPath; cssPath=_cssPath; } static void fill_old_character_definitions( void ) { for (size_t i = 0; i < sizeof(standardchar)/sizeof(CSTRDEF); i++) { const int nr = standardchar[i].nr; const char temp[3] = { nr / 256, nr % 256, 0 }; TQCString name( temp ); s_characterDefinitionMap.insert( name, StringDefinition( standardchar[i].slen, standardchar[i].st ) ); } } static char outbuffer[NULL_TERMINATED(HUGE_STR_MAX)]; static int no_newline_output=0; static int newline_for_fun=0; static bool output_possible=false; static const char *includedirs[] = { "/usr/include", "/usr/include/sys", "/usr/local/include", "/opt/local/include", "/usr/ccs", "/usr/X11R6/include", "/usr/openwin/include", "/usr/include/g++", 0 }; static bool ignore_links=false; static void add_links(char *c) { /* ** Add the links to the output. ** At the moment the following are recognized: ** ** name(*) -> ../man?/name.* ** method://string -> method://string ** www.host.name -> http://www.host.name ** ftp.host.name -> ftp://ftp.host.name ** name@host -> mailto:name@host ** <name.h> -> file:/usr/include/name.h (guess) ** ** Other possible links to add in the future: ** ** /dir/dir/file -> file:/dir/dir/file */ if (ignore_links) { output_real(c); return; } int i,j,nr; char *f, *h; char *g; const int numtests=6; // Nmber of tests char *idtest[numtests]; // url, mailto, www, ftp, manpage, C header file bool ok; /* search for (section) */ nr=0; idtest[0]=strstr(c+1,"://"); idtest[1]=strchr(c+1,'@'); idtest[2]=strstr(c,"www."); idtest[3]=strstr(c,"ftp."); idtest[4]=strchr(c+1,'('); idtest[5]=strstr(c+1,".h>"); for (i=0; i<numtests; ++i) nr += (idtest[i]!=NULL); while (nr) { j=-1; for (i=0; i<numtests; i++) if (idtest[i] && (j<0 || idtest[i]<idtest[j])) j=i; switch (j) { case 5: { /* <name.h> */ f=idtest[5]; h=f+2; g=f; while (g>c && g[-1]!=';') g--; bool wrote_include = false; if (g!=c) { TQCString dir; TQCString file(g, h - g + 1); file = file.stripWhiteSpace(); for (int index = 0; includedirs[index]; index++) { TQCString str = TQCString(includedirs[index]) + "/" + file; if (!access(str, R_OK)) { dir = includedirs[index]; break; } } if (!dir.isEmpty()) { char t; t=*g; *g=0; output_real(c); *g=t;*h=0; TQCString str; str.sprintf("<A HREF=\"file:%s/%s\">%s</A>>", dir.data(), file.data(), file.data()); output_real(str.data()); c=f+6; wrote_include = true; } } if (!wrote_include) { f[5]=0; output_real(c); f[5]=';'; c=f+5; } } break; case 4: /* manpage */ f=idtest[j]; /* check section */ g=strchr(f,')'); // The character before f must alphanumeric, the end of a HTML tag or the end of a if (g!=NULL && f>c && (g-f)<12 && (isalnum(f[-1]) || f[-1]=='>' || ( f[-1] == ';' ) ) && isdigit(f[1]) && f[1]!='0' && ((g-f)<=2 || isalpha(f[2]))) { ok = TRUE; h = f+2; while (h<g) { if (!isalnum(*h++)) { ok = FALSE; break; } } } else ok = false; h = f - 1; if ( ok ) { // Skip kdDebug(7107) << "BEFORE SECTION:" << *h << endl; if ( ( h > c + 5 ) && ( ! memcmp( h-5, " ", 6 ) ) ) { h -= 6; kdDebug(7107) << "Skip " << endl; } else if ( *h == ';' ) { // Not a non-breaking space, so probably not ok ok = false; } } if (ok) { /* this might be a link */ /* skip html makeup */ while (h>c && *h=='>') { while (h!=c && *h!='<') h--; if (h!=c) h--; } if (isalnum(*h)) { char t,sec, *e; TQString subsec; // ### TODO avoid using TQString, as we do not know the encoding TQString fstr(f); // ### TODO avoid using TQString, as we do not know the encoding e=h+1; sec=f[1]; subsec=f[2]; int index = fstr.find(')', 2); if (index != -1) subsec = fstr.mid(2, index - 2); else // No closing ')' found, take first character as subsection. subsec = fstr.mid(2, 1); while (h>c && (isalnum(h[-1]) || h[-1]=='_' || h[-1]==':' || h[-1]=='-' || h[-1]=='.')) h--; t=*h; *h='\0'; output_real(c); *h=t; t=*e; *e='\0'; TQCString str; if (subsec.isEmpty()) str.sprintf("<A HREF=\"man:%s(%c)\">%s</A>", h, sec, h); else str.sprintf("<A HREF=\"man:%s(%c%s)\">%s</A>", h, sec, subsec.lower().latin1(), h); output_real(str.data()); *e=t; c=e; } } *f='\0'; output_real(c); *f='('; idtest[4]=f-1; c=f; break; /* manpage */ case 3: /* ftp */ case 2: /* www */ g=f=idtest[j]; while (*g && (isalnum(*g) || *g=='_' || *g=='-' || *g=='+' || *g=='.' || *g=='/')) g++; if (g[-1]=='.') g--; if (g-f>4) { char t; t=*f; *f='\0'; output_real(c); *f=t; t=*g;*g='\0'; TQCString str; str.sprintf("<A HREF=\"%s://%s\">%s</A>", ((j==3)?"ftp":"http"), f, f); output_real(str.data()); *g=t; c=g; } else { f[3]='\0'; output_real(c); c=f+3; f[3]='.'; } break; case 1: /* mailto */ g=f=idtest[1]; while (g>c && (isalnum(g[-1]) || g[-1]=='_' || g[-1]=='-' || g[-1]=='+' || g[-1]=='.' || g[-1]=='%')) g--; if (g-7>=c && g[-1]==':') { // We have perhaps an email address starting with mailto: if (!tqstrncmp("mailto:",g-7,7)) g-=7; } h=f+1; while (*h && (isalnum(*h) || *h=='_' || *h=='-' || *h=='+' || *h=='.')) h++; if (*h=='.') h--; if (h-f>4 && f-g>1) { char t; t=*g; *g='\0'; output_real(c); *g=t;t=*h;*h='\0'; TQCString str; str.sprintf("<A HREF=\"mailto:%s\">%s</A>", g, g); output_real(str.data()); *h=t; c=h; } else { *f='\0'; output_real(c); *f='@'; idtest[1]=c; c=f; } break; case 0: /* url */ g=f=idtest[0]; while (g>c && isalpha(g[-1]) && islower(g[-1])) g--; h=f+3; while (*h && !isspace(*h) && *h!='<' && *h!='>' && *h!='"' && *h!='&') h++; if (f-g>2 && f-g<7 && h-f>3) { char t; t=*g; *g='\0'; output_real(c); *g=t; t=*h; *h='\0'; TQCString str; str.sprintf("<A HREF=\"%s\">%s</A>", g, g); output_real(str.data()); *h=t; c=h; } else { f[1]='\0'; output_real(c); f[1]='/'; c=f+1; } break; default: break; } nr=0; if (idtest[0] && idtest[0]<=c) idtest[0]=strstr(c+1,"://"); if (idtest[1] && idtest[1]<=c) idtest[1]=strchr(c+1,'@'); if (idtest[2] && idtest[2]<c) idtest[2]=strstr(c,"www."); if (idtest[3] && idtest[3]<c) idtest[3]=strstr(c,"ftp."); if (idtest[4] && idtest[4]<=c) idtest[4]=strchr(c+1,'('); if (idtest[5] && idtest[5]<=c) idtest[5]=strstr(c+1,".h>"); for (i=0; i<numtests; i++) nr += (idtest[i]!=NULL); } output_real(c); } static TQCString current_font; static int current_size=0; static int fillout=1; static void out_html(const char *c) { if (!c) return; // Added, probably due to the const? char *c2 = tqstrdup(c); char *c3 = c2; static int obp=0; if (no_newline_output) { int i=0; no_newline_output=1; while (c2[i]) { if (!no_newline_output) c2[i-1]=c2[i]; if (c2[i]=='\n') no_newline_output=0; i++; } if (!no_newline_output) c2[i-1]=0; } if (scaninbuff) { while (*c2) { if (buffpos>=buffmax) { char *h = new char[buffmax*2]; #ifdef SIMPLE_MAN2HTML if (!h) { cerr << "Memory full, cannot output!" << endl; exit(1); } #else // modern compiler do not return a NULL for a new #endif memcpy(h, buffer, buffmax); delete [] buffer; buffer=h; buffmax=buffmax*2; } buffer[buffpos++]=*c2++; } } else if (output_possible) { while (*c2) { outbuffer[obp++]=*c2; if (*c=='\n' || obp >= HUGE_STR_MAX) { outbuffer[obp]='\0'; add_links(outbuffer); obp=0; } c2++; } } delete [] c3; } static TQCString set_font( const TQCString& name ) { // Every font but R (Regular) creates <span> elements TQCString markup; if ( current_font != "R" && !current_font.isEmpty() ) markup += "</span>"; const uint len = name.length(); bool fontok = true; if ( len == 1 ) { const char lead = name[0]; switch (lead) { case 'P': // Palatino? case 'R': break; // regular, do nothing case 'I': markup += "<span style=\"font-style:italic\">"; break; case 'B': markup += "<span style=\"font-weight:bold\">"; break; case 'L': markup += "<span style=\"font-family:monospace\">"; break; // ### What's L? default: fontok = false; } } else if ( len == 2 ) { if ( name == "BI" ) markup += "<span style=\"font-style:italic;font-weight:bold\">"; // Courier else if ( name == "CR" ) markup += "<span style=\"font-family:monospace\">"; else if ( name == "CW" ) // CW is used by pod2man(1) (alias PerlDoc) markup += "<span style=\"font-family:monospace\">"; else if ( name == "CI" ) markup += "<span style=\"font-family:monospace;font-style:italic\">"; else if ( name == "CB" ) markup += "<span style=\"font-family:monospace;font-weight:bold\">"; // Times else if ( name == "TR" ) markup += "<span style=\"font-family:serif\">"; else if ( name == "TI" ) markup += "<span style=\"font-family:serif;font-style:italic\">"; else if ( name == "TB" ) markup += "<span style=\"font-family:serif;font-weight:bold\">"; // Helvetica else if ( name == "HR" ) markup += "<span style=\"font-family:sans-serif\">"; else if ( name == "HI" ) markup += "<span style=\"font-family:sans-serif;font-style:italic\">"; else if ( name == "HB" ) markup += "<span style=\"font-family:sans-serif;font-weight:bold\">"; else fontok = false; } else if ( len == 3 ) { if ( name == "CBI" ) markup += "<span style=\"font-family:monospace;font-style:italic;font-weight:bold\">"; else if ( name == "TBI" ) markup += "<span style=\"font-family:serif;font-style:italic;font-weight:bold\">"; else if ( name == "HBI" ) markup += "<span style=\"font-family:sans-serif;font-style:italic;font-weight:bold\">"; } if (fontok) current_font = name; else current_font = "R"; // Still nothing, then it is 'R' (Regular) return markup; } /// \deprecated static TQCString set_font( const char ch ) #ifndef SIMPLE_MAN2HTML KDE_DEPRECATED; static TQCString set_font( const char ch ) #endif { const TQCString name = &ch; return set_font( name ); } static TQCString change_to_size(int nr) { switch (nr) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': nr=nr-'0'; break; case '\0': break; default: nr=current_size+nr; if (nr>9) nr=9; if (nr< -9) nr=-9; break; } if ( nr == current_size ) return ""; const TQCString font ( current_font ); TQCString markup; markup = set_font("R"); if (current_size) markup += "</FONT>"; current_size=nr; if (nr) { markup += "<FONT SIZE=\""; if (nr>0) markup += '+'; else { markup += '-'; nr=-nr; } markup += char( nr + '0' ); markup += "\">"; } markup += set_font( font ); return markup; } /* static int asint=0; */ static int intresult=0; #define SKIPEOL while (*c && *c++!='\n') static bool skip_escape=false; static bool single_escape=false; static char *scan_escape_direct( char *c, TQCString& cstr ); /** * scan a named character * param c position */ static TQCString scan_named_character( char*& c ) { TQCString name; if ( *c == '(' ) { // \*(ab Name of two characters if ( c[1] == escapesym ) { TQCString cstr; c = scan_escape_direct( c+2, cstr ); // ### 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. name = cstr; } else { name+=c[1]; name+=c[2]; c+=3; } } else if ( *c == '[' ) { // \*[long_name] Long name // Named character groff(7) // We must find the ] to get a name c++; while ( *c && *c != ']' && *c != '\n' ) { if ( *c == escapesym ) { TQCString cstr; c = scan_escape_direct( c+1, cstr ); const int result = cstr.find(']'); if ( result == -1 ) name += cstr; else { // Note: we drop the characters after the ] name += cstr.left( result ); } } else { name+=*c; c++; } } if ( !*c || *c == '\n' ) { kdDebug(7107) << "Found linefeed! Could not parse character name: " << name << endl; return ""; } c++; } else if ( *c =='C' || c[1]== '\'' ) { // \C'name' c+=2; while ( *c && *c != '\'' && *c != '\n' ) { if ( *c == escapesym ) { TQCString cstr; c = scan_escape_direct( c+1, cstr ); const int result = cstr.find('\''); if ( result == -1 ) name += cstr; else { // Note: we drop the characters after the ] name += cstr.left( result ); } } else { name+=*c; c++; } } if ( !*c || *c == '\n' ) { kdDebug(7107) << "Found linefeed! Could not parse (\\C mode) character name: " << name << endl; return ""; } c++; } // Note: characters with a one character length name doe not exist, as they would collide with other escapes // Now we have the name, let us find it between the string names TQMap<TQCString,StringDefinition>::iterator it=s_characterDefinitionMap.find(name); if (it==s_characterDefinitionMap.end()) { kdDebug(7107) << "EXCEPTION: cannot find character with name: " << name << endl; // No output, as an undefined string is empty by default return ""; } else { kdDebug(7107) << "Character with name: \"" << name << "\" => " << (*it).m_output << endl; return (*it).m_output; } } static TQCString scan_named_string(char*& c) { TQCString name; if ( *c == '(' ) { // \*(ab Name of two characters if ( c[1] == escapesym ) { TQCString cstr; c = scan_escape_direct( c+2, cstr ); kdDebug(7107) << "\\(" << cstr << endl; // ### 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. name = cstr; } else { name+=c[1]; name+=c[2]; c+=3; } } else if ( *c == '[' ) { // \*[long_name] Long name // Named character groff(7) // We must find the ] to get a name c++; while ( *c && *c != ']' && *c != '\n' ) { if ( *c == escapesym ) { TQCString cstr; c = scan_escape_direct( c+1, cstr ); const int result = cstr.find(']'); if ( result == -1 ) name += cstr; else { // Note: we drop the characters after the ] name += cstr.left( result ); } } else { name+=*c; c++; } } if ( !*c || *c == '\n' ) { kdDebug(7107) << "Found linefeed! Could not parse string name: " << name << endl; return ""; } c++; } else { // \*a Name of one character name+=*c; c++; } // Now we have the name, let us find it between the string names TQMap<TQCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name); if (it==s_stringDefinitionMap.end()) { kdDebug(7107) << "EXCEPTION: cannot find string with name: " << name << endl; // No output, as an undefined string is empty by default return ""; } else { kdDebug(7107) << "String with name: \"" << name << "\" => " << (*it).m_output << endl; return (*it).m_output; } } static TQCString scan_dollar_parameter(char*& c) { unsigned int argno = 0; // No dollar argument number yet! if ( *c == '0' ) { //kdDebug(7107) << "$0" << endl; c++; return s_dollarZero; } else if ( *c >= '1' && *c <= '9' ) { //kdDebug(7107) << "$ direct" << endl; argno = ( *c - '0' ); c++; } else if ( *c == '(' ) { //kdDebug(7107) << "$(" << endl; if ( c[1] && c[2] && c[1] >= '0' && c[1] <= '9' && c[2] >= '0' && c[2] <= '9' ) { argno = ( c[1] - '0' ) * 10 + ( c[2] - '0' ); c += 3; } else { if ( !c[1] ) c++; else if ( !c[2] ) c+=2; else c += 3; return ""; } } else if ( *c == '[' ) { //kdDebug(7107) << "$[" << endl; argno = 0; c++; while ( *c && *c>='0' && *c<='9' && *c!=']' ) { argno *= 10; argno += ( *c - '0' ); c++; } if ( *c != ']' ) { return ""; } c++; } else if ( ( *c == '*' ) || ( *c == '@' ) ) { const bool quote = ( *c == '@' ); TQValueList<char*>::const_iterator it = s_argumentList.begin(); TQCString param; bool space = false; for ( ; it != s_argumentList.end(); ++it ) { if (space) param += " "; if (quote) param += '\"'; // Not as HTML, as it could be used by macros ! param += (*it); if (quote) param += '\"'; // Not as HTML, as it could be used by macros! space = true; } c++; return param; } else { kdDebug(7107) << "EXCEPTION: unknown parameter $" << *c << endl; return ""; } //kdDebug(7107) << "ARG $" << argno << endl; if ( !s_argumentList.isEmpty() && argno > 0 ) { //kdDebug(7107) << "ARG $" << argno << " OK!" << endl; argno--; if ( argno >= s_argumentList.size() ) { kdDebug(7107) << "EXCEPTION: cannot find parameter $" << (argno+1) << endl; return ""; } return s_argumentList[argno]; } return ""; } /// return the value of read-only number registers static int read_only_number_register( const TQCString& name ) { // Internal read-only variables if ( name == ".$" ) { kdDebug(7107) << "\\n[.$] == " << s_argumentList.size() << endl; return s_argumentList.size(); } else if ( name == ".g" ) return 0; // We are not groff(1) else if ( name == ".s" ) return current_size; #if 0 // ### TODO: map the fonts to a number else if ( name == ".f" ) return current_font; #endif else if ( name == ".P" ) return 0; // We are not printing else if ( name == ".A" ) return s_nroff; #ifndef SIMPLE_MAN2HTML // Special KDE KIO man: else if ( name == ".TDE_VERSION_MAJOR" ) return TDE_VERSION_MAJOR; else if ( name == ".TDE_VERSION_MINOR" ) return TDE_VERSION_MINOR; else if ( name == ".TDE_VERSION_RELEASE" ) return TDE_VERSION_RELEASE; else if ( name == ".TDE_VERSION" ) return TDE_VERSION; #endif // ### TODO: should .T be set to "html"? But we are not the HTML post-processor. :-( // ### TODO: groff defines much more read-only number registers #ifndef SIMPLE_MAN2HTML kdDebug(7107) << "EXCEPTION: unknown read-only number register: " << name << endl; #endif return 0; // Undefined variable } /// get the value of a number register and auto-increment if asked static int scan_number_register( char*& c) { int sign = 0; // Sign for auto-increment (if any) switch (*c) { case '+': sign = 1; c++; break; case '-': sign = -1; c++; break; default: break; } TQCString name; if ( *c == '[' ) { c++; if ( *c == '+' ) { sign = 1; c++; } else if ( *c == '-' ) { sign = -1; c++; } while ( *c && *c != ']' && *c != '\n' ) { // ### TODO: a \*[string] could be inside and should be processed name+=*c; c++; } if ( !*c || *c == '\n' ) { kdDebug(7107) << "Found linefeed! Could not parse number register name: " << name << endl; return 0; } c++; } else if ( *c == '(' ) { c++; if ( *c == '+' ) { sign = 1; c++; } else if ( *c == '-' ) { sign = -1; c++; } name+=c[0]; name+=c[1]; c+=2; } else { name += *c; c++; } if ( name[0] == '.' ) { return read_only_number_register( name ); } else { TQMap< TQCString, NumberDefinition >::iterator it = s_numberDefinitionMap.find( name ); if ( it == s_numberDefinitionMap.end() ) { return 0; // Undefined variable } else { (*it).m_value += sign * (*it).m_increment; return (*it).m_value; } } } /// get and set font static TQCString scan_named_font( char*& c ) { TQCString name; if ( *c == '(' ) { // \f(ab Name of two characters if ( c[1] == escapesym ) { TQCString cstr; c = scan_escape_direct( c+2, cstr ); kdDebug(7107) << "\\(" << cstr << endl; // ### 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. name = cstr; } else { name+=c[1]; name+=c[2]; c+=3; } } else if ( *c == '[' ) { // \f[long_name] Long name // We must find the ] to get a name c++; while ( *c && *c != ']' && *c != '\n' ) { if ( *c == escapesym ) { TQCString cstr; c = scan_escape_direct( c+1, cstr ); const int result = cstr.find(']'); if ( result == -1 ) name += cstr; else { // Note: we drop the characters after the ] name += cstr.left( result ); } } else { name+=*c; c++; } } if ( !*c || *c == '\n' ) { kdDebug(7107) << "Found linefeed! Could not parse font name: " << name << endl; return ""; } c++; } else { // \fa Font name with one character or one digit // ### HACK do *not* use: name = *c; or name would be empty name += *c; c++; } //kdDebug(7107) << "FONT NAME: " << name << endl; // Now we have the name, let us find the font bool ok = false; const unsigned int number = name.toUInt( &ok ); if ( ok ) { if ( number < 5 ) { const char* fonts[] = { "R", "I", "B", "BI", "CR" }; // Regular, Italic, Bold, Bold Italic, Courier regular name = fonts[ number ]; } else { kdDebug(7107) << "EXCEPTION: font has too big number: " << name << " => " << number << endl; name = "R"; // Let assume Regular } } else if ( name.isEmpty() ) { kdDebug(7107) << "EXCEPTION: font has no name: " << name << endl; name = "R"; // Let assume Regular } if ( !skip_escape ) return set_font( name ); else return ""; } static TQCString scan_number_code( char*& c ) { TQCString number; if ( *c != '\'' ) return ""; while ( *c && ( *c != '\n' ) && ( *c != '\'' ) ) { number += *c; c++; } bool ok = false; unsigned int result = number.toUInt( &ok ); if ( ( result < ' ' ) || ( result > 65535 ) ) return ""; else if ( result == '\t' ) { curpos += 8; curpos &= 0xfff8; return "\t"; } number.setNum( result ); number.prepend( "&#" ); number.append( ";" ); curpos ++; return number; } // ### TODO known missing escapes from groff(7): // ### TODO \& \! \) \: \R static char *scan_escape_direct( char *c, TQCString& cstr ) { bool exoutputp; bool exskipescape; int i,j; bool cplusplus = true; // Should the c++ be done at the end of the function cstr = ""; intresult=0; switch (*c) { case 'e': cstr = "\\"; curpos++;break; // ### FIXME: it should be the current escape symbol case '0': // ### TODO Where in Unicode? (space of digit width) case '~': // non-breakable-space (resizeable!) case ' ': case '|': // half-non-breakable-space case '^': // quarter-non-breakable-space cstr = " "; curpos++; break; case '"': SKIPEOL; c--; break; // ### TODO \# like \" but does not ignore the end of line (groff(7)) case '$': { c++; cstr = scan_dollar_parameter( c ); cplusplus = false; break; } case 'z': { c++; if (*c=='\\') { c=scan_escape_direct( c+1, cstr ); c--; } else cstr = TQCString( c, 1 ); break; } case 'k': c++; if (*c=='(') c+=2; // ### FIXME \k[REG] exists too case '!': case '%': case 'a': case 'd': case 'r': case 'u': case '\n': case '&': cstr = ""; break; case '(': case '[': case 'C': { // Do not go forward as scan_named_character needs the leading symbol cstr = scan_named_character( c ); cplusplus = false; break; } case '*': { c++; cstr = scan_named_string( c ); cplusplus = false; break; } case 'f': { c++; cstr = scan_named_font( c ); cplusplus = false; break; } case 's': // ### FIXME: many forms are missing c++; j=0;i=0; if (*c=='-') {j= -1; c++;} else if (*c=='+') {j=1; c++;} if (*c=='0') c++; else if (*c=='\\') { c++; c=scan_escape_direct( c, cstr ); i=intresult; if (!j) j=1; } else while (isdigit(*c) && (!i || (!j && i<4))) i=i*10+(*c++)-'0'; if (!j) { j=1; if (i) i=i-10; } if (!skip_escape) cstr=change_to_size(i*j); c--; break; case 'n': { c++; intresult = scan_number_register( c ); cplusplus = false; break; } case 'w': c++; i=*c; c++; exoutputp=output_possible; exskipescape=skip_escape; output_possible=false; skip_escape=true; j=0; while (*c!=i) { j++; if ( *c == escapesym ) c = scan_escape_direct( c+1, cstr); else c++; } output_possible=exoutputp; skip_escape=exskipescape; intresult=j; break; case 'l': cstr = "<HR>"; curpos=0; case 'b': case 'v': case 'x': case 'o': case 'L': case 'h': c++; i=*c; c++; exoutputp=output_possible; exskipescape=skip_escape; output_possible=0; skip_escape=true; while (*c != i) if (*c==escapesym) c=scan_escape_direct( c+1, cstr ); else c++; output_possible=exoutputp; skip_escape=exskipescape; break; case 'c': no_newline_output=1; break; case '{': newline_for_fun++; break; // Start conditional block case '}': if (newline_for_fun) newline_for_fun--; break; // End conditional block case 'p': cstr = "<BR>\n";curpos=0; break; case 't': cstr = "\t";curpos=(curpos+8)&0xfff8; break; case '<': cstr = "<";curpos++; break; case '>': cstr = ">";curpos++; break; case '\\': { if (single_escape) c--; else cstr="\\"; break; } case 'N': { c++; cstr = scan_number_code( c ); cplusplus = false; break; } #if 0 { if (*++c) c++; // c += 2 if (sscanf(c, "%d", &i) != 1) // (### FIXME ugly!) break; TQCString temp; temp.sprintf( "%d", i ); // Skip over number (### FIXME ugly!) c += temp.length(); switch(i) { case 8: cstr = "\t"; curpos=(curpos+8)&0xfff8; break; case 34: cstr = """; curpos++; break; default: cstr = char( i ); curpos++; break; } break; } #endif case '\'': cstr = "´";curpos++; break; // groff(7) ### TODO verify case '`': cstr = "`";curpos++; break; // groff(7) case '-': cstr = "-";curpos++; break; // groff(7) case '.': cstr = ".";curpos++; break; // groff(7) default: cstr = *c; curpos++; break; } if (cplusplus) c++; return c; } static char *scan_escape(char *c) { TQCString cstr; char* result = scan_escape_direct( c, cstr ); if ( !skip_escape ) out_html(cstr); return result; } class TABLEROW; class TABLEITEM { public: TABLEITEM(TABLEROW *row); ~TABLEITEM() { delete [] contents; } void setContents(const char *_contents) { delete [] contents; contents = tqstrdup(_contents); } const char *getContents() const { return contents; } void init() { delete [] contents; contents = 0; size = 0; align = 0; valign = 0; colspan = 1; rowspan = 1; font = 0; vleft = 0; vright = 0; space = 0; width = 0; } void copyLayout(const TABLEITEM *orig) { size = orig->size; align = orig->align; valign = orig->valign; colspan = orig->colspan; rowspan = orig->rowspan; font = orig->font; vleft = orig->vleft; vright = orig->vright; space = orig->space; width = orig->width; } public: int size,align,valign,colspan,rowspan,font,vleft,vright,space,width; private: char *contents; TABLEROW *_parent; }; class TABLEROW { char *test; public: TABLEROW() { test = new char; items.setAutoDelete(true); prev = 0; next = 0; } ~TABLEROW() { delete test; } int length() const { return items.count(); } bool has(int index) { return (index >= 0) && (index < (int)items.count()); } TABLEITEM &at(int index) { return *items.at(index); } TABLEROW *copyLayout() const; void addItem(TABLEITEM *item) { items.append(item); } TABLEROW *prev, *next; private: TQPtrList<TABLEITEM> items; }; TABLEITEM::TABLEITEM(TABLEROW *row) : contents(0), _parent(row) { init(); _parent->addItem(this); } TABLEROW *TABLEROW::copyLayout() const { TABLEROW *newrow = new TABLEROW(); TQPtrListIterator<TABLEITEM> it(items); for ( ; it.current(); ++it) { TABLEITEM *newitem = new TABLEITEM(newrow); newitem->copyLayout(it.current()); } return newrow; } static const char *tableopt[]= { "center", "expand", "box", "allbox", "doublebox", "tab", "linesize", "delim", NULL }; static int tableoptl[] = { 6,6,3,6,9,3,8,5,0}; static void clear_table(TABLEROW *table) { TABLEROW *tr1,*tr2; tr1=table; while (tr1->prev) tr1=tr1->prev; while (tr1) { tr2=tr1; tr1=tr1->next; delete tr2; } } static char *scan_expression(char *c, int *result); static char *scan_format(char *c, TABLEROW **result, int *maxcol) { TABLEROW *layout, *currow; TABLEITEM *curfield; int i,j; if (*result) { clear_table(*result); } layout= currow=new TABLEROW(); curfield=new TABLEITEM(currow); while (*c && *c!='.') { switch (*c) { case 'C': case 'c': case 'N': case 'n': case 'R': case 'r': case 'A': case 'a': case 'L': case 'l': case 'S': case 's': case '^': case '_': if (curfield->align) curfield=new TABLEITEM(currow); curfield->align=toupper(*c); c++; break; case 'i': case 'I': case 'B': case 'b': curfield->font = toupper(*c); c++; break; case 'f': case 'F': c++; curfield->font = toupper(*c); c++; if (!isspace(*c) && *c!='.') c++; break; case 't': case 'T': curfield->valign='t'; c++; break; case 'p': case 'P': c++; i=j=0; if (*c=='+') { j=1; c++; } if (*c=='-') { j=-1; c++; } while (isdigit(*c)) i=i*10+(*c++)-'0'; if (j) curfield->size= i*j; else curfield->size=j-10; break; case 'v': case 'V': case 'w': case 'W': c=scan_expression(c+2,&curfield->width); break; case '|': if (curfield->align) curfield->vleft++; else curfield->vright++; c++; break; case 'e': case 'E': c++; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': i=0; while (isdigit(*c)) i=i*10+(*c++)-'0'; curfield->space=i; break; case ',': case '\n': currow->next=new TABLEROW(); currow->next->prev=currow; currow=currow->next; currow->next=NULL; curfield=new TABLEITEM(currow); c++; break; default: c++; break; } } if (*c=='.') while (*c++!='\n'); *maxcol=0; currow=layout; while (currow) { i=currow->length(); if (i>*maxcol) *maxcol=i; currow=currow->next; } *result=layout; return c; } static TABLEROW *next_row(TABLEROW *tr) { if (tr->next) { tr=tr->next; if (!tr->next) return next_row(tr); return tr; } else { tr->next = tr->copyLayout(); tr->next->prev = tr; return tr->next; } } static char itemreset[20]="\\fR\\s0"; #define FORWARDCUR do { curfield++; } while (currow->has(curfield) && currow->at(curfield).align=='S'); static char *scan_table(char *c) { char *h; char *g; int center=0, expand=0, box=0, border=0, linesize=1; int i,j,maxcol=0, finished=0; TQCString oldfont; int oldsize,oldfillout; char itemsep='\t'; TABLEROW *layout=NULL, *currow; int curfield = -1; while (*c++!='\n'); h=c; if (*h=='.') return c-1; oldfont=current_font; oldsize=current_size; oldfillout=fillout; out_html(set_font("R")); out_html(change_to_size(0)); if (!fillout) { fillout=1; out_html("</PRE>"); } while (*h && *h!='\n') h++; if (h[-1]==';') { /* scan table options */ while (c<h) { while (isspace(*c)) c++; for (i=0; tableopt[i] && tqstrncmp(tableopt[i],c,tableoptl[i]);i++); c=c+tableoptl[i]; switch (i) { case 0: center=1; break; case 1: expand=1; break; case 2: box=1; break; case 3: border=1; break; case 4: box=2; break; case 5: while (*c++!='('); itemsep=*c++; break; case 6: while (*c++!='('); linesize=0; while (isdigit(*c)) linesize=linesize*10+(*c++)-'0'; break; case 7: while (*c!=')') c++; default: break; } c++; } c=h+1; } /* scan layout */ c=scan_format(c,&layout, &maxcol); // currow=layout; currow=next_row(layout); curfield=0; i=0; while (!finished && *c) { /* search item */ h=c; if ((*c=='_' || *c=='=') && (c[1]==itemsep || c[1]=='\n')) { if (c[-1]=='\n' && c[1]=='\n') { if (currow->prev) { currow->prev->next=new TABLEROW(); currow->prev->next->next=currow; currow->prev->next->prev=currow->prev; currow->prev=currow->prev->next; } else { currow->prev=layout=new TABLEROW(); currow->prev->prev=NULL; currow->prev->next=currow; } TABLEITEM *newitem = new TABLEITEM(currow->prev); newitem->align=*c; newitem->colspan=maxcol; curfield=0; c=c+2; } else { if (currow->has(curfield)) { currow->at(curfield).align=*c; FORWARDCUR; } if (c[1]=='\n') { currow=next_row(currow); curfield=0; } c=c+2; } } else if (*c=='T' && c[1]=='{') { h=c+2; c=strstr(h,"\nT}"); c++; *c='\0'; g=NULL; scan_troff(h,0,&g); scan_troff(itemreset, 0, &g); *c='T'; c+=3; if (currow->has(curfield)) { currow->at(curfield).setContents(g); FORWARDCUR; } delete [] g; if (c[-1]=='\n') { currow=next_row(currow); curfield=0; } } else if (*c=='.' && c[1]=='T' && c[2]=='&' && c[-1]=='\n') { TABLEROW *hr; while (*c++!='\n'); hr=currow; currow=currow->prev; hr->prev=NULL; c=scan_format(c,&hr, &i); hr->prev=currow; currow->next=hr; currow=hr; next_row(currow); curfield=0; } else if (*c=='.' && c[1]=='T' && c[2]=='E' && c[-1]=='\n') { finished=1; while (*c++!='\n'); if (currow->prev) currow->prev->next=NULL; currow->prev=NULL; clear_table(currow); currow = 0; } else if (*c=='.' && c[-1]=='\n' && !isdigit(c[1])) { /* skip troff request inside table (usually only .sp ) */ while (*c++!='\n'); } else { h=c; while (*c && (*c!=itemsep || c[-1]=='\\') && (*c!='\n' || c[-1]=='\\')) c++; i=0; if (*c==itemsep) {i=1; *c='\n'; } if (h[0]=='\\' && h[2]=='\n' && (h[1]=='_' || h[1]=='^')) { if (currow->has(curfield)) { currow->at(curfield).align=h[1]; FORWARDCUR; } h=h+3; } else { g=NULL; h=scan_troff(h,1,&g); scan_troff(itemreset,0, &g); if (currow->has(curfield)) { currow->at(curfield).setContents(g); FORWARDCUR; } delete [] g; } if (i) *c=itemsep; c=h; if (c[-1]=='\n') { currow=next_row(currow); curfield=0; } } } /* calculate colspan and rowspan */ currow=layout; while (currow->next) currow=currow->next; while (currow) { int ti = 0, ti1 = 0, ti2 = -1; TABLEROW *prev = currow->prev; if (!prev) break; while (prev->has(ti1)) { if (currow->has(ti)) switch (currow->at(ti).align) { case 'S': if (currow->has(ti2)) { currow->at(ti2).colspan++; if (currow->at(ti2).rowspan<prev->at(ti1).rowspan) currow->at(ti2).rowspan=prev->at(ti1).rowspan; } break; case '^': if (prev->has(ti1)) prev->at(ti1).rowspan++; default: if (ti2 < 0) ti2=ti; else { do { ti2++; } while (currow->has(ti2) && currow->at(ti2).align=='S'); } break; } ti++; if (ti1 >= 0) ti1++; } currow=currow->prev; } /* produce html output */ if (center) out_html("<CENTER>"); if (box==2) out_html("<TABLE BORDER><TR><TD>"); out_html("<TABLE"); if (box || border) { out_html(" BORDER"); if (!border) out_html("><TR><TD><TABLE"); if (expand) out_html(" WIDTH=\"100%\""); } out_html(">\n"); currow=layout; while (currow) { j=0; out_html("<TR VALIGN=top>"); curfield=0; while (currow->has(curfield)) { if (currow->at(curfield).align!='S' && currow->at(curfield).align!='^') { out_html("<TD"); switch (currow->at(curfield).align) { case 'N': currow->at(curfield).space+=4; case 'R': out_html(" ALIGN=right"); break; case 'C': out_html(" ALIGN=center"); default: break; } if (!currow->at(curfield).valign && currow->at(curfield).rowspan>1) out_html(" VALIGN=center"); if (currow->at(curfield).colspan>1) { char buf[5]; out_html(" COLSPAN="); sprintf(buf, "%i", currow->at(curfield).colspan); out_html(buf); } if (currow->at(curfield).rowspan>1) { char buf[5]; out_html(" ROWSPAN="); sprintf(buf, "%i", currow->at(curfield).rowspan); out_html(buf); } j=j+currow->at(curfield).colspan; out_html(">"); if (currow->at(curfield).size) out_html(change_to_size(currow->at(curfield).size)); if (currow->at(curfield).font) out_html(set_font(currow->at(curfield).font)); switch (currow->at(curfield).align) { case '=': out_html("<HR><HR>"); break; case '_': out_html("<HR>"); break; default: out_html(currow->at(curfield).getContents()); break; } if (currow->at(curfield).space) for (i=0; i<currow->at(curfield).space;i++) out_html(" "); if (currow->at(curfield).font) out_html(set_font("R")); if (currow->at(curfield).size) out_html(change_to_size(0)); if (j>=maxcol && currow->at(curfield).align>'@' && currow->at(curfield).align!='_') out_html("<BR>"); out_html("</TD>"); } curfield++; } out_html("</TR>\n"); currow=currow->next; } clear_table(layout); if (box && !border) out_html("</TABLE>"); out_html("</TABLE>"); if (box==2) out_html("</TABLE>"); if (center) out_html("</CENTER>\n"); else out_html("\n"); if (!oldfillout) out_html("<PRE>"); fillout=oldfillout; out_html(change_to_size(oldsize)); out_html(set_font(oldfont)); return c; } static char *scan_expression( char *c, int *result, const unsigned int numLoop ) { int value=0,value2,sign=1,opex=0; char oper='c'; if (*c=='!') { c=scan_expression(c+1, &value); value= (!value); } else if (*c=='n') { c++; value=s_nroff; } else if (*c=='t') { c++; value=1-s_nroff; } else if (*c=='\'' || *c=='"' || *c<' ' || (*c=='\\' && c[1]=='(')) { /* ?string1?string2? ** test if string1 equals string2. */ char *st1=NULL, *st2=NULL, *h; char *tcmp=NULL; char sep; sep=*c; if (sep=='\\') { tcmp=c; c=c+3; } c++; h=c; while (*c!= sep && (!tcmp || tqstrncmp(c,tcmp,4))) c++; *c='\n'; scan_troff(h, 1, &st1); *c=sep; if (tcmp) c=c+3; c++; h=c; while (*c!=sep && (!tcmp || tqstrncmp(c,tcmp,4))) c++; *c='\n'; scan_troff(h,1,&st2); *c=sep; if (!st1 && !st2) value=1; else if (!st1 || !st2) value=0; else value=(!qstrcmp(st1, st2)); delete [] st1; delete [] st2; if (tcmp) c=c+3; c++; } else { while (*c && ( !isspace(*c) || ( numLoop > 0 ) ) && *c!=')' && opex >= 0) { opex=0; switch (*c) { case '(': c = scan_expression( c + 1, &value2, numLoop + 1 ); value2=sign*value2; opex=1; break; case '.': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { int num=0,denum=1; value2=0; while (isdigit(*c)) value2=value2*10+((*c++)-'0'); if (*c=='.' && isdigit(c[1])) { c++; while (isdigit(*c)) { num=num*10+((*c++)-'0'); denum=denum*10; } } if (isalpha(*c)) { /* scale indicator */ switch (*c) { case 'i': /* inch -> 10pt */ value2=value2*10+(num*10+denum/2)/denum; num=0; break; default: break; } c++; } value2=value2+(num+denum/2)/denum; value2=sign*value2; opex=1; if (*c=='.') opex = -1; } break; case '\\': c=scan_escape(c+1); value2=intresult*sign; if (isalpha(*c)) c++; /* scale indicator */ opex=1; break; case '-': if (oper) { sign=-1; c++; break; } case '>': case '<': case '+': case '/': case '*': case '%': case '&': case '=': case ':': if (c[1]=='=') oper=(*c++) +16; else oper=*c; c++; break; default: c++; break; } if (opex > 0) { sign=1; switch (oper) { case 'c': value=value2; break; case '-': value=value-value2; break; case '+': value=value+value2; break; case '*': value=value*value2; break; case '/': if (value2) value=value/value2; break; case '%': if (value2) value=value%value2; break; case '<': value=(value<value2); break; case '>': value=(value>value2); break; case '>'+16: value=(value>=value2); break; case '<'+16: value=(value<=value2); break; case '=': case '='+16: value=(value==value2); break; case '&': value = (value && value2); break; case ':': value = (value || value2); break; default: { kdDebug(7107) << "Unknown operator " << char(oper) << endl; } } oper=0; } } if (*c==')') c++; } *result=value; return c; } static char *scan_expression(char *c, int *result) { return scan_expression( c, result, 0 ); } static void trans_char(char *c, char s, char t) { char *sl=c; int slash=0; while (*sl!='\n' || slash) { if (!slash) { if (*sl==escapesym) slash=1; else if (*sl==s) *sl=t; } else slash=0; sl++; } } // 2004-10-19, patched by Waldo Bastian <bastian@kde.org>: // Fix handling of lines like: // .TH FIND 1L \" -*- nroff -*- // Where \" indicates the start of comment. // // The problem is the \" handling in fill_words(), the return value // indicates the end of the word as well as the end of the line, which makes it // basically impossible to express that the end of the last word is not the end of // the line. // // I have corrected that by adding an extra parameter 'next_line' that returns a // pointer to the next line, while the function itself returns a pointer to the end // of the last word. static char *fill_words(char *c, char *words[], int *n, bool newline, char **next_line) { char *sl=c; int slash=0; int skipspace=0; *n=0; words[*n]=sl; while (*sl && (*sl!='\n' || slash)) { if (!slash) { if (*sl=='"') { if (skipspace && (*(sl+1)=='"')) *sl++ = '\a'; else { *sl='\a'; skipspace=!skipspace; } } else if (*sl==escapesym) { slash=1; if (sl[1]=='\n') *sl='\a'; } else if ((*sl==' ' || *sl=='\t') && !skipspace) { if (newline) *sl='\n'; if (words[*n]!=sl) (*n)++; words[*n]=sl+1; } } else { if (*sl=='"') { sl--; if (newline) *sl='\n'; if (words[*n]!=sl) (*n)++; if (next_line) { char *eow = sl; sl++; while (*sl && *sl !='\n') sl++; *next_line = sl; return eow; } return sl; } slash=0; } sl++; } if (sl!=words[*n]) (*n)++; if (next_line) *next_line = sl+1; return sl; } static const char *abbrev_list[] = { "GSBG", "Getting Started ", "SUBG", "Customizing SunOS", "SHBG", "Basic Troubleshooting", "SVBG", "SunView User's Guide", "MMBG", "Mail and Messages", "DMBG", "Doing More with SunOS", "UNBG", "Using the Network", "GDBG", "Games, Demos & Other Pursuits", "CHANGE", "SunOS 4.1 Release Manual", "INSTALL", "Installing SunOS 4.1", "ADMIN", "System and Network Administration", "SECUR", "Security Features Guide", "PROM", "PROM User's Manual", "DIAG", "Sun System Diagnostics", "SUNDIAG", "Sundiag User's Guide", "MANPAGES", "SunOS Reference Manual", "REFMAN", "SunOS Reference Manual", "SSI", "Sun System Introduction", "SSO", "System Services Overview", "TEXT", "Editing Text Files", "DOCS", "Formatting Documents", "TROFF", "Using <B>nroff</B> and <B>troff</B>", "INDEX", "Global Index", "CPG", "C Programmer's Guide", "CREF", "C Reference Manual", "ASSY", "Assembly Language Reference", "PUL", "Programming Utilities and Libraries", "DEBUG", "Debugging Tools", "NETP", "Network Programming", "DRIVER", "Writing Device Drivers", "STREAMS", "STREAMS Programming", "SBDK", "SBus Developer's Kit", "WDDS", "Writing Device Drivers for the SBus", "FPOINT", "Floating-Point Programmer's Guide", "SVPG", "SunView 1 Programmer's Guide", "SVSPG", "SunView 1 System Programmer's Guide", "PIXRCT", "Pixrect Reference Manual", "CGI", "SunCGI Reference Manual", "CORE", "SunCore Reference Manual", "4ASSY", "Sun-4 Assembly Language Reference", "SARCH", "<FONT SIZE=\"-1\">SPARC</FONT> Architecture Manual", "KR", "The C Programming Language", NULL, NULL }; static const char *lookup_abbrev(char *c) { int i=0; if (!c) return ""; while (abbrev_list[i] && qstrcmp(c,abbrev_list[i])) i=i+2; if (abbrev_list[i]) return abbrev_list[i+1]; else return c; } static const char *section_list[] = { #ifdef Q_OS_SOLARIS // for Solaris "1", "User Commands", "1B", "SunOS/BSD Compatibility Package Commands", "1b", "SunOS/BSD Compatibility Package Commands", "1C", "Communication Commands ", "1c", "Communication Commands", "1F", "FMLI Commands ", "1f", "FMLI Commands", "1G", "Graphics and CAD Commands ", "1g", "Graphics and CAD Commands ", "1M", "Maintenance Commands", "1m", "Maintenance Commands", "1S", "SunOS Specific Commands", "1s", "SunOS Specific Commands", "2", "System Calls", "3", "C Library Functions", "3B", "SunOS/BSD Compatibility Library Functions", "3b", "SunOS/BSD Compatibility Library Functions", "3C", "C Library Functions", "3c", "C Library Functions", "3E", "C Library Functions", "3e", "C Library Functions", "3F", "Fortran Library Routines", "3f", "Fortran Library Routines", "3G", "C Library Functions", "3g", "C Library Functions", "3I", "Wide Character Functions", "3i", "Wide Character Functions", "3K", "Kernel VM Library Functions", "3k", "Kernel VM Library Functions", "3L", "Lightweight Processes Library", "3l", "Lightweight Processes Library", "3M", "Mathematical Library", "3m", "Mathematical Library", "3N", "Network Functions", "3n", "Network Functions", "3R", "Realtime Library", "3r", "Realtime Library", "3S", "Standard I/O Functions", "3s", "Standard I/O Functions", "3T", "Threads Library", "3t", "Threads Library", "3W", "C Library Functions", "3w", "C Library Functions", "3X", "Miscellaneous Library Functions", "3x", "Miscellaneous Library Functions", "4", "File Formats", "4B", "SunOS/BSD Compatibility Package File Formats", "4b", "SunOS/BSD Compatibility Package File Formats", "5", "Headers, Tables, and Macros", "6", "Games and Demos", "7", "Special Files", "7B", "SunOS/BSD Compatibility Special Files", "7b", "SunOS/BSD Compatibility Special Files", "8", "Maintenance Procedures", "8C", "Maintenance Procedures", "8c", "Maintenance Procedures", "8S", "Maintenance Procedures", "8s", "Maintenance Procedures", "9", "DDI and DKI", "9E", "DDI and DKI Driver Entry Points", "9e", "DDI and DKI Driver Entry Points", "9F", "DDI and DKI Kernel Functions", "9f", "DDI and DKI Kernel Functions", "9S", "DDI and DKI Data Structures", "9s", "DDI and DKI Data Structures", "L", "Local Commands", #elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) "1", "General Commands", "2", "System Calls", "3", "Library Functions", "4", "Kernel Interfaces", "5", "File Formats", "6", "Games", "7", "Miscellaneous Information", "8", "System Manager's Manuals", "9", "Kernel Developer's Manuals", #else // Other OS "1", "User Commands ", "1C", "User Commands", "1G", "User Commands", "1S", "User Commands", "1V", "User Commands ", "2", "System Calls", "2V", "System Calls", "3", "C Library Functions", "3C", "Compatibility Functions", "3F", "Fortran Library Routines", "3K", "Kernel VM Library Functions", "3L", "Lightweight Processes Library", "3M", "Mathematical Library", "3N", "Network Functions", "3R", "RPC Services Library", "3S", "Standard I/O Functions", "3V", "C Library Functions", "3X", "Miscellaneous Library Functions", "4", "Devices and Network Interfaces", "4F", "Protocol Families", "4I", "Devices and Network Interfaces", "4M", "Devices and Network Interfaces", "4N", "Devices and Network Interfaces", "4P", "Protocols", "4S", "Devices and Network Interfaces", "4V", "Devices and Network Interfaces", "5", "File Formats", "5V", "File Formats", "6", "Games and Demos", "7", "Environments, Tables, and Troff Macros", "7V", "Environments, Tables, and Troff Macros", "8", "Maintenance Commands", "8C", "Maintenance Commands", "8S", "Maintenance Commands", "8V", "Maintenance Commands", "L", "Local Commands", #endif // The defaults NULL, "Misc. Reference Manual Pages", NULL, NULL }; static const char *section_name(char *c) { int i=0; if (!c) return ""; while (section_list[i] && qstrcmp(c,section_list[i])) i=i+2; if (section_list[i+1]) return section_list[i+1]; else return c; } static char *skip_till_newline(char *c) { int lvl=0; while (*c && (*c!='\n' || lvl>0)) { if (*c=='\\') { c++; if (*c=='}') lvl--; else if (*c=='{') lvl++; } c++; } if (*c) c++; if (lvl<0 && newline_for_fun) { newline_for_fun = newline_for_fun+lvl; if (newline_for_fun<0) newline_for_fun=0; } return c; } static bool s_whileloop = false; /// Processing the .while request static void request_while( char*& c, int j, bool mdoc ) { // ### TODO: .break and .continue kdDebug(7107) << "Entering .while" << endl; c += j; char* newline = skip_till_newline( c ); const char oldchar = *newline; *newline = 0; // We store the full .while stuff into a TQCString as if it would be a macro const TQCString macro = c ; kdDebug(7107) << "'Macro' of .while" << endl << macro << endl; // Prepare for continuing after .while loop end *newline = oldchar; c = newline; // Process -while loop const bool oldwhileloop = s_whileloop; s_whileloop = true; int result = true; // It must be an int due to the call to scan_expression while ( result ) { // Unlike for a normal macro, we have the condition at start, so we do not need to prepend extra bytes char* liveloop = tqstrdup( macro.data() ); kdDebug(7107) << "Scanning .while condition" << endl; kdDebug(7101) << "Loop macro " << liveloop << endl; char* end_expression = scan_expression( liveloop, &result ); kdDebug(7101) << "After " << end_expression << endl; if ( result ) { kdDebug(7107) << "New .while iteration" << endl; // The condition is true, so call the .while's content char* help = end_expression + 1; while ( *help && ( *help == ' ' || *help == '\t' ) ) ++help; if ( ! *help ) { // We have a problem, so stop .while result = false; break; } if ( mdoc ) scan_troff_mandoc( help, false, 0 ); else scan_troff( help, false, 0 ); } delete[] liveloop; } // s_whileloop = oldwhileloop; kdDebug(7107) << "Ending .while" << endl; } const int max_wordlist = 100; /// Processing mixed fonts reqiests like .BI static void request_mixed_fonts( char*& c, int j, const char* font1, const char* font2, const bool mode, const bool inFMode ) { c += j; if (*c=='\n') c++; int words; char *wordlist[max_wordlist]; fill_words(c, wordlist, &words, true, &c); for (int i=0; i<words; i++) { if ((mode) || (inFMode)) { out_html(" "); curpos++; } wordlist[i][-1]=' '; out_html( set_font( (i&1) ? font2 : font1 ) ); scan_troff(wordlist[i],1,NULL); } out_html(set_font("R")); if (mode) { out_html(" ]"); curpos++; } out_html(NEWLINE); if (!fillout) curpos=0; else curpos++; } // Some known missing requests from man(7): // - see "safe subset": .tr // Some known missing requests from mdoc(7): // - start or end of quotings // Some of the requests are from mdoc. // On Linux see the man pages mdoc(7), mdoc.samples(7) and groff_mdoc(7) // See also the online man pages of FreeBSD: mdoc(7) #define REQ_UNKNOWN -1 #define REQ_ab 0 #define REQ_di 1 #define REQ_ds 2 #define REQ_as 3 #define REQ_br 4 #define REQ_c2 5 #define REQ_cc 6 #define REQ_ce 7 #define REQ_ec 8 #define REQ_eo 9 #define REQ_ex 10 #define REQ_fc 11 #define REQ_fi 12 #define REQ_ft 13 // groff(7) "FonT" #define REQ_el 14 #define REQ_ie 15 #define REQ_if 16 #define REQ_ig 17 #define REQ_nf 18 #define REQ_ps 19 #define REQ_sp 20 #define REQ_so 21 #define REQ_ta 22 #define REQ_ti 23 #define REQ_tm 24 #define REQ_B 25 #define REQ_I 26 #define REQ_Fd 27 #define REQ_Fn 28 #define REQ_Fo 29 #define REQ_Fc 30 #define REQ_OP 31 #define REQ_Ft 32 #define REQ_Fa 33 #define REQ_BR 34 #define REQ_BI 35 #define REQ_IB 36 #define REQ_IR 37 #define REQ_RB 38 #define REQ_RI 39 #define REQ_DT 40 #define REQ_IP 41 // man(7) "Indent Paragraph" #define REQ_TP 42 #define REQ_IX 43 #define REQ_P 44 #define REQ_LP 45 #define REQ_PP 46 #define REQ_HP 47 #define REQ_PD 48 #define REQ_Rs 49 #define REQ_RS 50 #define REQ_Re 51 #define REQ_RE 52 #define REQ_SB 53 #define REQ_SM 54 #define REQ_Ss 55 #define REQ_SS 56 #define REQ_Sh 57 #define REQ_SH 58 // man(7) "Sub Header" #define REQ_Sx 59 #define REQ_TS 60 #define REQ_Dt 61 #define REQ_TH 62 #define REQ_TX 63 #define REQ_rm 64 #define REQ_rn 65 #define REQ_nx 66 #define REQ_in 67 #define REQ_nr 68 // groff(7) "Number Register" #define REQ_am 69 #define REQ_de 70 #define REQ_Bl 71 // mdoc(7) "Begin List" #define REQ_El 72 // mdoc(7) "End List" #define REQ_It 73 // mdoc(7) "ITem" #define REQ_Bk 74 #define REQ_Ek 75 #define REQ_Dd 76 #define REQ_Os 77 // mdoc(7) #define REQ_Bt 78 #define REQ_At 79 // mdoc(7) "AT&t" (not parsable, not callable) #define REQ_Fx 80 // mdoc(7) "Freebsd" (not parsable, not callable) #define REQ_Nx 81 #define REQ_Ox 82 #define REQ_Bx 83 // mdoc(7) "Bsd" #define REQ_Ux 84 // mdoc(7) "UniX" #define REQ_Dl 85 #define REQ_Bd 86 #define REQ_Ed 87 #define REQ_Be 88 #define REQ_Xr 89 // mdoc(7) "eXternal Reference" #define REQ_Fl 90 // mdoc(7) "FLag" #define REQ_Pa 91 #define REQ_Pf 92 #define REQ_Pp 93 #define REQ_Dq 94 // mdoc(7) "Double Quote" #define REQ_Op 95 #define REQ_Oo 96 #define REQ_Oc 97 #define REQ_Pq 98 // mdoc(7) "Parenthese Quote" #define REQ_Ql 99 #define REQ_Sq 100 // mdoc(7) "Single Quote" #define REQ_Ar 101 #define REQ_Ad 102 #define REQ_Em 103 // mdoc(7) "EMphasis" #define REQ_Va 104 #define REQ_Xc 105 #define REQ_Nd 106 #define REQ_Nm 107 #define REQ_Cd 108 #define REQ_Cm 109 #define REQ_Ic 110 #define REQ_Ms 111 #define REQ_Or 112 #define REQ_Sy 113 #define REQ_Dv 114 #define REQ_Ev 115 #define REQ_Fr 116 #define REQ_Li 117 #define REQ_No 118 #define REQ_Ns 119 #define REQ_Tn 120 #define REQ_nN 121 #define REQ_perc_A 122 #define REQ_perc_D 123 #define REQ_perc_N 124 #define REQ_perc_O 125 #define REQ_perc_P 126 #define REQ_perc_Q 127 #define REQ_perc_V 128 #define REQ_perc_B 129 #define REQ_perc_J 130 #define REQ_perc_R 131 #define REQ_perc_T 132 #define REQ_An 133 // mdoc(7) "Author Name" #define REQ_Aq 134 // mdoc(7) "Angle bracket Quote" #define REQ_Bq 135 // mdoc(7) "Bracket Quote" #define REQ_Qq 136 // mdoc(7) "straight double Quote" #define REQ_UR 137 // man(7) "URl" #define REQ_UE 138 // man(7) "Url End" #define REQ_UN 139 // man(7) "Url Name" (a.k.a. anchors) #define REQ_troff 140 // groff(7) "TROFF mode" #define REQ_nroff 141 // groff(7) "NROFF mode" #define REQ_als 142 // groff(7) "ALias String" #define REQ_rr 143 // groff(7) "Remove number Register" #define REQ_rnn 144 // groff(7) "ReName Number register" #define REQ_aln 145 // groff(7) "ALias Number register" #define REQ_shift 146 // groff(7) "SHIFT parameter" #define REQ_while 147 // groff(7) "WHILE loop" #define REQ_do 148 // groff(7) "DO command" #define REQ_Dx 149 // mdoc(7) "DragonFly" macro static int get_request(char *req, int len) { static const char *requests[] = { "ab", "di", "ds", "as", "br", "c2", "cc", "ce", "ec", "eo", "ex", "fc", "fi", "ft", "el", "ie", "if", "ig", "nf", "ps", "sp", "so", "ta", "ti", "tm", "B", "I", "Fd", "Fn", "Fo", "Fc", "OP", "Ft", "Fa", "BR", "BI", "IB", "IR", "RB", "RI", "DT", "IP", "TP", "IX", "P", "LP", "PP", "HP", "PD", "Rs", "RS", "Re", "RE", "SB", "SM", "Ss", "SS", "Sh", "SH", "Sx", "TS", "Dt", "TH", "TX", "rm", "rn", "nx", "in", "nr", "am", "de", "Bl", "El", "It", "Bk", "Ek", "Dd", "Os", "Bt", "At", "Fx", "Nx", "Ox", "Bx", "Ux", "Dl", "Bd", "Ed", "Be", "Xr", "Fl", "Pa", "Pf", "Pp", "Dq", "Op", "Oo", "Oc", "Pq", "Ql", "Sq", "Ar", "Ad", "Em", "Va", "Xc", "Nd", "Nm", "Cd", "Cm", "Ic", "Ms", "Or", "Sy", "Dv", "Ev", "Fr", "Li", "No", "Ns", "Tn", "nN", "%A", "%D", "%N", "%O", "%P", "%Q", "%V", "%B", "%J", "%R", "%T", "An", "Aq", "Bq", "Qq", "UR", "UE", "UN", "troff", "nroff", "als", "rr", "rnn", "aln", "shift", "while", "do", "Dx", 0 }; int r = 0; while (requests[r] && tqstrncmp(req, requests[r], len)) r++; return requests[r] ? r : REQ_UNKNOWN; } // &%(#@ c programs !!! //static int ifelseval=0; // If/else can be nested! static TQValueStack<int> s_ifelseval; // Process a (mdoc) request involving quotes static char* process_quote(char* c, int j, const char* open, const char* close) { trans_char(c,'"','\a'); c+=j; if (*c=='\n') c++; // ### TODO: why? Quote requests cannot be empty! out_html(open); c=scan_troff_mandoc(c,1,0); out_html(close); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; return c; } /** * Is the char \p ch a puntuaction in sence of mdoc(7) */ static bool is_mdoc_punctuation( const char ch ) { if ( ( ch >= '0' && ch <= '9' ) || ( ch >='A' && ch <='Z' ) || ( ch >= 'a' && ch <= 'z' ) ) return false; else if ( ch == '.' || ch == ',' || ch == ';' || ch == ':' || ch == '(' || ch == ')' || ch == '[' || ch == ']' ) return true; else return false; } /** * Can the char \p c be part of an identifier * \note For groff, an identifier can consist of nearly all ASCII printable non-white-space characters * See info:/groff/Identifiers */ static bool is_identifier_char( const char c ) { if ( c >= '!' && c <= '[' ) // Include digits and upper case return true; else if ( c >= ']' && c <= '~' ) // Include lower case return true; else if ( c== '\\' ) return false; // ### TODO: it should be treated as escape instead! return false; } static TQCString scan_identifier( char*& c ) { char* h = c; // help pointer // ### TODO Groff seems to eat nearly everything as identifier name (info:/groff/Identifiers) while ( *h && *h != '\a' && *h != '\n' && is_identifier_char( *h ) ) ++h; const char tempchar = *h; *h = 0; const TQCString name = c; *h = tempchar; if ( name.isEmpty() ) { kdDebug(7107) << "EXCEPTION: identifier empty!" << endl; } c = h; return name; } static char *scan_request(char *c) { // mdoc(7) stuff static bool mandoc_synopsis=false; /* True if we are in the synopsis section */ static bool mandoc_command=false; /* True if this is mdoc(7) page */ static int mandoc_bd_options; /* Only copes with non-nested Bd's */ static int function_argument=0; // Number of function argument (.Fo, .Fa, .Fc) // man(7) stuff static bool ur_ignore=false; // Has .UR a parameter : (for .UE to know if or not to write </a>) int i=0; bool mode=false; char *h=0; char *wordlist[max_wordlist]; int words; char *sl; while (*c==' ' || *c=='\t') c++; // Spaces or tabs allowed between control character and request if (c[0]=='\n') return c+1; if (c[0]==escapesym) { /* some pages use .\" .\$1 .\} */ /* .\$1 is too difficult/stuppid */ if (c[1]=='$') { kdDebug(7107) << "Found .\\$" << endl; c=skip_till_newline(c); // ### TODO } else c = scan_escape(c+1); } else { int nlen = 0; TQCString macroName; while (c[nlen] && (c[nlen] != ' ') && (c[nlen] != '\t') && (c[nlen] != '\n') && (c[nlen] != escapesym)) { macroName+=c[nlen]; nlen++; } int j = nlen; while (c[j] && c[j]==' ' || c[j]=='\t') j++; /* search macro database of self-defined macros */ TQMap<TQCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(macroName); if (it!=s_stringDefinitionMap.end()) { kdDebug(7107) << "CALLING MACRO: " << macroName << endl; const TQCString oldDollarZero = s_dollarZero; // Previous value of $0 s_dollarZero = macroName; sl=fill_words(c+j, wordlist, &words, true, &c); *sl='\0'; for (i=1;i<words; i++) wordlist[i][-1]='\0'; for (i=0; i<words; i++) { char *h=NULL; if (mandoc_command) scan_troff_mandoc(wordlist[i],1,&h); else scan_troff(wordlist[i],1,&h); wordlist[i] = tqstrdup(h); delete [] h; } for ( i=words; i<max_wordlist; i++ ) wordlist[i]=NULL; if ( !(*it).m_output.isEmpty() ) { //kdDebug(7107) << "Macro content is: " << endl << (*it).m_output << endl; const unsigned int length = (*it).m_output.length(); char* work = new char [length+2]; work[0] = '\n'; // The macro must start after an end of line to allow a request on first line tqstrncpy(work+1,(*it).m_output.data(),length+1); const TQValueList<char*> oldArgumentList( s_argumentList ); s_argumentList.clear(); for ( i = 0 ; i < max_wordlist; i++ ) { if (!wordlist[i]) break; s_argumentList.push_back( wordlist[i] ); } const int onff=newline_for_fun; if (mandoc_command) scan_troff_mandoc( work + 1, 0, NULL ); else scan_troff( work + 1, 0, NULL); delete[] work; newline_for_fun=onff; s_argumentList = oldArgumentList; } for (i=0; i<words; i++) delete [] wordlist[i]; *sl='\n'; s_dollarZero = oldDollarZero; kdDebug(7107) << "ENDING MACRO: " << macroName << endl; } else { kdDebug(7107) << "REQUEST: " << macroName << endl; switch (int request = get_request(c, nlen)) { case REQ_ab: // groff(7) "ABort" { h=c+j; while (*h && *h !='\n') h++; *h='\0'; if (scaninbuff && buffpos) { buffer[buffpos]='\0'; kdDebug(7107) << "ABORT: " << buffer << endl; } // ### TODO find a way to display it to the user kdDebug(7107) << "Aborting: .ab " << (c+j) << endl; return 0; break; } case REQ_An: // mdoc(7) "Author Name" { c+=j; c=scan_troff_mandoc(c,1,0); break; } case REQ_di: // groff(7) "end current DIversion" { kdDebug(7107) << "Start .di" << endl; c+=j; if (*c=='\n') { ++c; break; } const TQCString name ( scan_identifier( c ) ); while (*c && *c!='\n') c++; c++; h=c; while (*c && tqstrncmp(c,".di",3)) while (*c && *c++!='\n'); *c='\0'; char* result=0; scan_troff(h,0,&result); TQMap<TQCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name); if (it==s_stringDefinitionMap.end()) { StringDefinition def; def.m_length=0; def.m_output=result; s_stringDefinitionMap.insert(name,def); } else { (*it).m_length=0; (*it).m_output=result; } delete[] result; if (*c) *c='.'; c=skip_till_newline(c); kdDebug(7107) << "end .di" << endl; break; } case REQ_ds: // groff(7) "Define String variable" mode=true; case REQ_as: // groff (7) "Append String variable" { kdDebug(7107) << "start .ds/.as" << endl; int oldcurpos=curpos; c+=j; const TQCString name( scan_identifier( c) ); if ( name.isEmpty() ) break; while (*c && isspace(*c)) c++; if (*c && *c=='"') c++; single_escape=true; curpos=0; char* result=0; c=scan_troff(c,1,&result); TQMap<TQCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name); if (it==s_stringDefinitionMap.end()) { StringDefinition def; def.m_length=curpos; def.m_output=result; s_stringDefinitionMap.insert(name,def); } else { if (mode) { // .ds Defining String (*it).m_length=curpos; (*it).m_output=result; } else { // .as Appending String (*it).m_length+=curpos; (*it).m_output+=result; } } delete[] result; single_escape=false; curpos=oldcurpos; kdDebug(7107) << "end .ds/.as" << endl; break; } case REQ_br: // groff(7) "line BReak" { if (still_dd) out_html("<DD>"); // ### VERIFY (does not look like generating good HTML) else out_html("<BR>\n"); curpos=0; c=c+j; if (c[0]==escapesym) c=scan_escape(c+1); c=skip_till_newline(c); break; } case REQ_c2: // groff(7) "reset non-break Control character" (2 means non-break) { c=c+j; if (*c!='\n') nobreaksym=*c; else nobreaksym='\''; c=skip_till_newline(c); break; } case REQ_cc: // groff(7) "reset Control Character" { c=c+j; if (*c!='\n') controlsym=*c; else controlsym='.'; c=skip_till_newline(c); break; } case REQ_ce: // groff (7) "CEnter" { c=c+j; if (*c=='\n') i=1; else { i=0; while ('0'<=*c && *c<='9') { i=i*10+*c-'0'; c++; } } c=skip_till_newline(c); /* center next i lines */ if (i>0) { out_html("<CENTER>\n"); while (i && *c) { char *line=NULL; c=scan_troff(c,1, &line); if (line && tqstrncmp(line, "<BR>", 4)) { out_html(line); out_html("<BR>\n"); delete [] line; // ### FIXME: memory leak! i--; } } out_html("</CENTER>\n"); curpos=0; } break; } case REQ_ec: // groff(7) "reset Escape Character" { c=c+j; if (*c!='\n') escapesym=*c; else escapesym='\\'; break; c=skip_till_newline(c); } case REQ_eo: // groff(7) "turn Escape character Off" { escapesym='\0'; c=skip_till_newline(c); break; } case REQ_ex: // groff(7) "EXit" { return 0; break; } case REQ_fc: // groff(7) "set Field and pad Character" { c=c+j; if (*c=='\n') fieldsym=padsym='\0'; else { fieldsym=c[0]; padsym=c[1]; } c=skip_till_newline(c); break; } case REQ_fi: // groff(7) "FIll" { if (!fillout) { out_html(set_font("R")); out_html(change_to_size('0')); out_html("</PRE>\n"); } curpos=0; fillout=1; c=skip_till_newline(c); break; } case REQ_ft: // groff(7) "FonT" { c += j; h = skip_till_newline( c ); const char oldChar = *h; *h = 0; const TQCString name = c; // ### TODO: name might contain a variable if ( name.isEmpty() ) out_html( set_font( "P" ) ); // Previous font else out_html( set_font( name ) ); *h = oldChar; c = h; break; } case REQ_el: // groff(7) "ELse" { int ifelseval = s_ifelseval.pop(); /* .el anything : else part of if else */ if (ifelseval) { c=c+j; c[-1]='\n'; c=scan_troff(c,1,NULL); } else c=skip_till_newline(c+j); break; } case REQ_ie: // groff(7) "If with Else" /* .ie c anything : then part of if else */ case REQ_if: // groff(7) "IF" { /* .if c anything * .if !c anything * .if N anything * .if !N anything * .if 'string1'string2' anything * .if !'string1'string2' anything */ c=c+j; c=scan_expression(c, &i); if (request == REQ_ie) { int ifelseval=!i; s_ifelseval.push( ifelseval ); } if (i) { *c='\n'; c++; c=scan_troff(c,1,NULL); } else c=skip_till_newline(c); break; } case REQ_ig: // groff(7) "IGnore" { const char *endwith="..\n"; i=3; c=c+j; if (*c!='\n' && *c != '\\') { /* Not newline or comment */ endwith=c-1;i=1; c[-1]='.'; while (*c && *c!='\n') c++,i++; } c++; while (*c && tqstrncmp(c,endwith,i)) while (*c++!='\n'); while (*c && *c++!='\n'); break; } case REQ_nf: // groff(7) "No Filling" { if (fillout) { out_html(set_font("R")); out_html(change_to_size('0')); out_html("<PRE>\n"); } curpos=0; fillout=0; c=skip_till_newline(c); break; } case REQ_ps: // groff(7) "previous Point Size" { c=c+j; if (*c=='\n') out_html(change_to_size('0')); else { j=0; i=0; if (*c=='-') { j= -1; c++; } else if (*c=='+') j=1;c++; c=scan_expression(c, &i); if (!j) { j=1; if (i>5) i=i-10; } out_html(change_to_size(i*j)); } c=skip_till_newline(c); break; } case REQ_sp: // groff(7) "SKip one line" { c=c+j; if (fillout) out_html("<br><br>"); else { out_html(NEWLINE); } curpos=0; c=skip_till_newline(c); break; } case REQ_so: // groff(7) "Include SOurce file" { char *buf; char *name=NULL; curpos=0; c=c+j; if (*c=='/') h=c; else { h=c-3; h[0]='.'; h[1]='.'; h[2]='/'; } while (*c!='\n') c++; *c='\0'; scan_troff(h,1, &name); if (name[3]=='/') h=name+3; else h=name; /* this works alright, except for section 3 */ buf=read_man_page(h); if (!buf) { kdDebug(7107) << "Unable to open or read file: .so " << (h) << endl; out_html("<BLOCKQUOTE>" "man2html: unable to open or read file.\n"); out_html(h); out_html("</BLOCKQUOTE>\n"); } else scan_troff(buf+1,0,NULL); delete [] buf; delete [] name; *c++='\n'; break; } case REQ_ta: // gorff(7) "set TAbulators" { c=c+j; j=0; while (*c!='\n') { sl=scan_expression(c, &tabstops[j]); if (j>0 && (*c=='-' || *c=='+')) tabstops[j]+=tabstops[j-1]; c=sl; while (*c==' ' || *c=='\t') c++; j++; } maxtstop=j; curpos=0; break; } case REQ_ti: // groff(7) "Temporary Indent" { /*while (itemdepth || dl_set[itemdepth]) { out_html("</DL>\n"); if (dl_set[itemdepth]) dl_set[itemdepth]=0; else itemdepth--; }*/ out_html("<BR>\n"); c=c+j; c=scan_expression(c, &j); for (i=0; i<j; i++) out_html(" "); curpos=j; c=skip_till_newline(c); break; } case REQ_tm: // groff(7) "TerMinal" ### TODO: what are useful uses for it { c=c+j; h=c; while (*c!='\n') c++; *c='\0'; kdDebug(7107) << ".tm " << (h) << endl; *c='\n'; break; } case REQ_B: // man(7) "Bold" mode=1; case REQ_I: // man(7) "Italic" { /* parse one line in a certain font */ out_html( set_font( mode?"B":"I" ) ); fill_words(c, wordlist, &words, false, 0); c=c+j; if (*c=='\n') c++; c=scan_troff(c, 1, NULL); out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_Fd: // mdoc(7) "Function Definition" { // Normal text must be printed in bold, punctuation in regular font c+=j; if (*c=='\n') c++; // ### TODO: verify sl=fill_words(c, wordlist, &words, true, &c); for (i=0; i<words; i++) { wordlist[i][-1]=' '; // ### FIXME In theory, only a single punctuation character is recognized as punctuation if ( is_mdoc_punctuation ( *wordlist[i] ) ) out_html( set_font ( "R" ) ); else out_html( set_font ( "B" ) ); scan_troff(wordlist[i],1,NULL); out_html(" "); } // In the mdoc synopsis, there are automatical line breaks (### TODO: before or after?) if (mandoc_synopsis) { out_html("<br>"); }; out_html(set_font("R")); out_html(NEWLINE); if (!fillout) curpos=0; else curpos++; break; } case REQ_Fn: // mdoc(7) for "Function calls" { // brackets and commas have to be inserted automatically c+=j; if (*c=='\n') c++; sl=fill_words(c, wordlist, &words, true, &c); if ( words ) { for (i=0; i<words; i++) { wordlist[i][-1]=' '; if ( i ) out_html( set_font( "I" ) ); else out_html( set_font( "B" ) ); scan_troff(wordlist[i],1,NULL); out_html( set_font( "R" ) ); if (i==0) { out_html(" ("); } else if (i<words-1) out_html(", "); } out_html(")"); } out_html(set_font("R")); if (mandoc_synopsis) out_html("<br>"); out_html(NEWLINE); if (!fillout) curpos=0; else curpos++; break; } case REQ_Fo: // mdoc(7) "Function definition Opening" { char* font[2] = { "B", "R" }; c+=j; if (*c=='\n') c++; char *eol=strchr(c,'\n'); char *semicolon=strchr(c,';'); if ((semicolon!=0) && (semicolon<eol)) *semicolon=' '; sl=fill_words(c, wordlist, &words, true, &c); // Normally a .Fo has only one parameter for (i=0; i<words; i++) { wordlist[i][-1]=' '; out_html(set_font(font[i&1])); scan_troff(wordlist[i],1,NULL); if (i==0) { out_html(" ("); } // ### TODO What should happen if there is more than one argument // else if (i<words-1) out_html(", "); } function_argument=1; // Must be > 0 out_html(set_font("R")); out_html(NEWLINE); if (!fillout) curpos=0; else curpos++; break; } case REQ_Fc:// mdoc(7) "Function definition Close" { // .Fc has no parameter c+=j; c=skip_till_newline(c); char* font[2] = { "B", "R" }; out_html(set_font(font[i&1])); out_html(")"); out_html(set_font("R")); if (mandoc_synopsis) out_html("<br>"); out_html(NEWLINE); if (!fillout) curpos=0; else curpos++; function_argument=0; // Reset the count variable break; } case REQ_Fa: // mdoc(7) "Function definition argument" { char* font[2] = { "B", "R" }; c+=j; if (*c=='\n') c++; sl=fill_words(c, wordlist, &words, true, &c); out_html(set_font(font[i&1])); // function_argument==0 means that we had no .Fo before, e.g. in mdoc.samples(7) if (function_argument > 1) { out_html(", "); curpos+=2; function_argument++; } else if (function_argument==1) { // We are only at the first parameter function_argument++; } for (i=0; i<words; i++) { wordlist[i][-1]=' '; scan_troff(wordlist[i],1,NULL); } out_html(set_font("R")); if (!fillout) curpos=0; else curpos++; break; } case REQ_OP: /* groff manpages use this construction */ { /* .OP a b : [ <B>a</B> <I>b</I> ] */ mode=true; out_html(set_font("R")); out_html("["); curpos++; request_mixed_fonts( c, j, "B", "I", true, false ); break; // Do not break! } case REQ_Ft: //perhaps "Function return type" { request_mixed_fonts( c, j, "B", "I", false, true ); break; } case REQ_BR: { request_mixed_fonts( c, j, "B", "R", false, false ); break; } case REQ_BI: { request_mixed_fonts( c, j, "B", "I", false, false ); break; } case REQ_IB: { request_mixed_fonts( c, j, "I", "B", false, false ); break; } case REQ_IR: { request_mixed_fonts( c, j, "I", "R", false, false ); break; } case REQ_RB: { request_mixed_fonts( c, j, "R", "B", false, false ); break; } case REQ_RI: { request_mixed_fonts( c, j, "R", "I", false, false ); break; } case REQ_DT: // man(7) "Default Tabulators" { for (j=0;j<20; j++) tabstops[j]=(j+1)*8; maxtstop=20; c=skip_till_newline(c); break; } case REQ_IP: // man(7) "Ident Paragraph" { sl=fill_words(c+j, wordlist, &words, true, &c); if (!dl_set[itemdepth]) { out_html("<DL>\n"); dl_set[itemdepth]=1; } out_html("<DT>"); if (words) scan_troff(wordlist[0], 1,NULL); out_html("<DD>"); curpos=0; break; } case REQ_TP: // man(7) "hanging Tag Paragraph" { if (!dl_set[itemdepth]) { out_html("<br><br><DL>\n"); dl_set[itemdepth]=1; } out_html("<DT>"); c=skip_till_newline(c); /* somewhere a definition ends with '.TP' */ if (!*c) still_dd=true; else { // HACK for proc(5) while (c[0]=='.' && c[1]=='\\' && c[2]=='\"') { // We have a comment, so skip the line c=skip_till_newline(c); } c=scan_troff(c,1,NULL); out_html("<DD>"); } curpos=0; break; } case REQ_IX: // "INdex" ### TODO: where is it defined? { /* general index */ c=skip_till_newline(c); break; } case REQ_P: // man(7) "Paragraph" case REQ_LP:// man(7) "Paragraph" case REQ_PP:// man(7) "Paragraph; reset Prevailing indent" { if (dl_set[itemdepth]) { out_html("</DL>\n"); dl_set[itemdepth]=0; } if (fillout) out_html("<br><br>\n"); else { out_html(NEWLINE); } curpos=0; c=skip_till_newline(c); break; } case REQ_HP: // man(7) "Hanging indent Paragraph" { if (!dl_set[itemdepth]) { out_html("<DL>"); dl_set[itemdepth]=1; } out_html("<DT>\n"); still_dd=true; c=skip_till_newline(c); curpos=0; break; } case REQ_PD: // man(7) "Paragraph Distance" { c=skip_till_newline(c); break; } case REQ_Rs: // mdoc(7) "Relative margin Start" case REQ_RS: // man(7) "Relative margin Start" { sl=fill_words(c+j, wordlist, &words, true, 0); j=1; if (words>0) scan_expression(wordlist[0], &j); if (j>=0) { itemdepth++; dl_set[itemdepth]=0; out_html("<DL><DT><DD>"); c=skip_till_newline(c); curpos=0; break; } } case REQ_Re: // mdoc(7) "Relative margin End" case REQ_RE: // man(7) "Relative margin End" { if (itemdepth > 0) { if (dl_set[itemdepth]) out_html("</DL>"); out_html("</DL>\n"); itemdepth--; } c=skip_till_newline(c); curpos=0; break; } case REQ_SB: // man(7) "Small; Bold" { out_html(set_font("B")); out_html("<small>"); trans_char(c,'"','\a'); // ### VERIFY c=scan_troff(c+j, 1, NULL); out_html("</small>"); out_html(set_font("R")); break; } case REQ_SM: // man(7) "SMall" { c=c+j; if (*c=='\n') c++; out_html("<small>"); trans_char(c,'"','\a'); // ### VERIFY c=scan_troff(c,1,NULL); out_html("</small>"); break; } case REQ_Ss: // mdoc(7) "Sub Section" mandoc_command = 1; case REQ_SS: // mdoc(7) "Sub Section" mode=true; case REQ_Sh: // mdoc(7) "Sub Header" /* hack for fallthru from above */ mandoc_command = !mode || mandoc_command; case REQ_SH: // man(7) "Sub Header" { c=c+j; if (*c=='\n') c++; while (itemdepth || dl_set[itemdepth]) { out_html("</DL>\n"); if (dl_set[itemdepth]) dl_set[itemdepth]=0; else if (itemdepth > 0) itemdepth--; } out_html(set_font("R")); out_html(change_to_size(0)); if (!fillout) { fillout=1; out_html("</PRE>"); } trans_char(c,'"', '\a'); if (section) { out_html("</div>\n"); section=0; } if (mode) out_html("\n<H3>"); else out_html("\n<H2>"); mandoc_synopsis = tqstrncmp(c, "SYNOPSIS", 8) == 0; c = mandoc_command ? scan_troff_mandoc(c,1,NULL) : scan_troff(c,1,NULL); if (mode) out_html("</H3>\n"); else out_html("</H2>\n"); out_html("<div>\n"); section=1; curpos=0; break; } case REQ_Sx: // mdoc(7) { // reference to a section header out_html(set_font("B")); trans_char(c,'"','\a'); c=c+j; if (*c=='\n') c++; c=scan_troff(c, 1, NULL); out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_TS: // ### TODO where is it defined? (tbl?) { c=scan_table(c); break; } case REQ_Dt: /* mdoc(7) */ mandoc_command = true; case REQ_TH: // man(7) "Title Header" { if (!output_possible) { sl = fill_words(c+j, wordlist, &words, true, &c); // ### TODO: the page should be displayed even if it is "anonymous" (words==0) if (words>=1) { for (i=1; i<words; i++) wordlist[i][-1]='\0'; *sl='\0'; for (i=0; i<words; i++) { if (wordlist[i][0] == '\007') wordlist[i]++; if (wordlist[i][tqstrlen(wordlist[i])-1] == '\007') wordlist[i][tqstrlen(wordlist[i])-1] = 0; } output_possible=true; out_html( DOCTYPE"<HTML>\n<HEAD>\n"); #ifdef SIMPLE_MAN2HTML // Most English man pages are in ISO-8859-1 out_html("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n"); #else // kio_man transforms from local to UTF-8 out_html("<meta http-equiv=\"Content-Type\" content=\"text/html; charset="); out_html(TQTextCodec::codecForLocale()->mimeName()); out_html("\">\n"); #endif out_html("<TITLE>"); out_html(scan_troff(wordlist[0], 0, NULL)); out_html( " Manpage</TITLE>\n"); out_html( "<link rel=\"stylesheet\" href=\""); out_html(htmlPath); out_html("/kde-default.css\" type=\"text/css\">\n" ); out_html( "<meta name=\"ROFF Type\" content=\""); if (mandoc_command) out_html("mdoc"); else out_html("man"); out_html("\">\n"); out_html( "</HEAD>\n\n" ); out_html("<BODY BGCOLOR=\"#FFFFFF\">\n\n" ); out_html("<div style=\"background-image: url("); out_html(cssPath); out_html("/top-middle.png); width: 100%; height: 131pt;\">\n" ); out_html("<div style=\"position: absolute; right: 0pt;\">\n"); out_html("<img src=\""); out_html(htmlPath); out_html("/top-right-konqueror.png\" style=\"margin: 0pt\" alt=\"Top right\">\n"); out_html("</div>\n"); out_html("<div style=\"position: absolute; left: 0pt;\">\n"); out_html("<img src=\""); out_html(htmlPath); out_html("/top-left.png\" style=\"margin: 0pt\" alt=\"Top left\">\n"); out_html("</div>\n"); 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"); out_html( scan_troff(wordlist[0], 0, NULL ) ); out_html("</div>\n"); out_html("</div>\n"); out_html("<div style=\"margin-left: 5em; margin-right: 5em;\">\n"); out_html("<h1>" ); out_html( scan_troff(wordlist[0], 0, NULL ) ); out_html( "</h1>\n" ); if (words>1) { out_html("Section: " ); if (!mandoc_command && words>4) out_html(scan_troff(wordlist[4], 0, NULL) ); else out_html(section_name(wordlist[1])); out_html(" ("); out_html(scan_troff(wordlist[1], 0, NULL)); out_html(")\n"); } else { out_html("Section not specified"); } *sl='\n'; } } else { kdWarning(7107) << ".TH found but output not possible" << endl; c=skip_till_newline(c); } curpos=0; break; } case REQ_TX: // mdoc(7) { sl=fill_words(c+j, wordlist, &words, true, &c); *sl='\0'; out_html(set_font("I")); if (words>1) wordlist[1][-1]='\0'; const char *c2=lookup_abbrev(wordlist[0]); curpos+=tqstrlen(c2); out_html(c2); out_html(set_font("R")); if (words>1) out_html(wordlist[1]); *sl='\n'; break; } case REQ_rm: // groff(7) "ReMove" /* .rm xx : Remove request, macro or string */ mode=true; case REQ_rn: // groff(7) "ReName" /* .rn xx yy : Rename request, macro or string xx to yy */ { kdDebug(7107) << "start .rm/.rn" << endl; c+=j; const TQCString name( scan_identifier( c ) ); if ( name.isEmpty() ) { kdDebug(7107) << "EXCEPTION: empty origin string to remove/rename: " << endl; break; } TQCString name2; if ( !mode ) { while (*c && isspace(*c) && *c!='\n') ++c; name2 = scan_identifier( c ); if ( name2.isEmpty() ) { kdDebug(7107) << "EXCEPTION: empty destination string to rename: " << endl; break; } } c=skip_till_newline(c); TQMap<TQCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name); if (it==s_stringDefinitionMap.end()) { kdDebug(7107) << "EXCEPTION: cannot find string to rename or remove: " << name << endl; } else { if (mode) { // .rm ReMove s_stringDefinitionMap.remove(name); // ### QT4: removeAll } else { // .rn ReName StringDefinition def=(*it); s_stringDefinitionMap.remove(name); // ### QT4: removeAll s_stringDefinitionMap.insert(name2,def); } } kdDebug(7107) << "end .rm/.rn" << endl; break; } case REQ_nx: // ### TODO in man(7) it is "No filling", not "next file" /* .nx filename : next file. */ case REQ_in: // groff(7) "INdent" { /* .in +-N : Indent */ c=skip_till_newline(c); break; } case REQ_nr: // groff(7) "Number Register" { kdDebug(7107) << "start .nr" << endl; c += j; const TQCString name( scan_identifier( c ) ); if ( name.isEmpty() ) { kdDebug(7107) << "EXCEPTION: empty name for register variable" << endl; break; } while ( *c && ( *c==' ' || *c=='\t' ) ) c++; int sign = 0; if ( *c && ( *c == '+' || *c == '-' ) ) { if ( *c == '+' ) sign = 1; else if ( *c == '-' ) sign = -1; } int value = 0; int increment = 0; c=scan_expression( c, &value ); if ( *c && *c!='\n') { while ( *c && ( *c==' ' || *c=='\t' ) ) c++; c=scan_expression( c, &increment ); } c = skip_till_newline( c ); TQMap <TQCString, NumberDefinition>::iterator it = s_numberDefinitionMap.find( name ); if ( it == s_numberDefinitionMap.end() ) { if ( sign < 1 ) value = -value; NumberDefinition def( value, increment ); s_numberDefinitionMap.insert( name, def ); } else { if ( sign > 0 ) (*it).m_value += value; else if ( sign < 0 ) (*it).m_value += - value; else (*it).m_value = value; (*it).m_increment = increment; } kdDebug(7107) << "end .nr" << endl; break; } case REQ_am: // groff(7) "Append Macro" /* .am xx yy : append to a macro. */ /* define or handle as .ig yy */ mode=true; case REQ_de: // groff(7) "DEfine macro" /* .de xx yy : define or redefine macro xx; end at .yy (..) */ /* define or handle as .ig yy */ { kdDebug(7107) << "Start .am/.de" << endl; c+=j; char *next_line; sl = fill_words(c, wordlist, &words, true, &next_line); char *nameStart = wordlist[0]; c = nameStart; while (*c && (*c != ' ') && (*c != '\n')) c++; *c = '\0'; const TQCString name(nameStart); TQCString endmacro; if (words == 1) { endmacro=".."; } else { endmacro='.'; c = wordlist[1]; while (*c && (*c != ' ') && (*c != '\n')) endmacro+=*c++; } c = next_line; sl=c; const int length=tqstrlen(endmacro); while (*c && tqstrncmp(c,endmacro,length)) c=skip_till_newline(c); TQCString macro; while (sl!=c) { if (sl[0]=='\\' && sl[1]=='\\') { macro+='\\'; sl++; } else macro+=*sl; sl++; } TQMap<TQCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name); if (it==s_stringDefinitionMap.end()) { StringDefinition def; def.m_length=0; def.m_output=macro; s_stringDefinitionMap.insert(name,def); } else if (mode) { // .am Append Macro (*it).m_length=0; // It could be formerly a string // if ((*it).m_output.right(1)!='\n') if (*((*it).m_output.right(1).data())!='\n') (*it).m_output+='\n'; (*it).m_output+=macro; } else { // .de DEfine macro (*it).m_length=0; // It could be formerly a string (*it).m_output=macro; } c=skip_till_newline(c); kdDebug(7107) << "End .am/.de" << endl; break; } case REQ_Bl: // mdoc(7) "Begin List" { char list_options[NULL_TERMINATED(MED_STR_MAX)]; char *nl = strchr(c,'\n'); c=c+j; if (dl_set[itemdepth]) /* These things can nest. */ itemdepth++; if (nl) { /* Parse list options */ strlimitcpy(list_options, c, nl - c, MED_STR_MAX); } if (strstr(list_options, "-bullet")) { /* HTML Unnumbered List */ dl_set[itemdepth] = BL_BULLET_LIST; out_html("<UL>\n"); } else if (strstr(list_options, "-enum")) { /* HTML Ordered List */ dl_set[itemdepth] = BL_ENUM_LIST; out_html("<OL>\n"); } else { /* HTML Descriptive List */ dl_set[itemdepth] = BL_DESC_LIST; out_html("<DL>\n"); } if (fillout) out_html("<br><br>\n"); else { out_html(NEWLINE); } curpos=0; c=skip_till_newline(c); break; } case REQ_El: // mdoc(7) "End List" { c=c+j; if (dl_set[itemdepth] & BL_DESC_LIST) out_html("</DL>\n"); else if (dl_set[itemdepth] & BL_BULLET_LIST) out_html("</UL>\n"); else if (dl_set[itemdepth] & BL_ENUM_LIST) out_html("</OL>\n"); dl_set[itemdepth]=0; if (itemdepth > 0) itemdepth--; if (fillout) out_html("<br><br>\n"); else { out_html(NEWLINE); } curpos=0; c=skip_till_newline(c); break; } case REQ_It: // mdoc(7) "list ITem" { c=c+j; if (tqstrncmp(c, "Xo", 2) == 0 && isspace(*(c+2))) c = skip_till_newline(c); if (dl_set[itemdepth] & BL_DESC_LIST) { out_html("<DT>"); out_html(set_font("B")); if (*c=='\n') { /* Don't allow embedded comms after a newline */ c++; c=scan_troff(c,1,NULL); } else { /* Do allow embedded comms on the same line. */ c=scan_troff_mandoc(c,1,NULL); } out_html(set_font("R")); out_html(NEWLINE); out_html("<DD>"); } else if (dl_set[itemdepth] & (BL_BULLET_LIST | BL_ENUM_LIST)) { out_html("<LI>"); c=scan_troff_mandoc(c,1,NULL); out_html(NEWLINE); } if (fillout) curpos++; else curpos=0; break; } case REQ_Bk: /* mdoc(7) */ case REQ_Ek: /* mdoc(7) */ case REQ_Dd: /* mdoc(7) */ case REQ_Os: // mdoc(7) "Operating System" { trans_char(c,'"','\a'); c=c+j; if (*c=='\n') c++; c=scan_troff_mandoc(c, 1, NULL); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_Bt: // mdoc(7) "Beta Test" { trans_char(c,'"','\a'); c=c+j; out_html(" is currently in beta test."); if (fillout) curpos++; else curpos=0; break; } case REQ_At: /* mdoc(7) */ case REQ_Fx: /* mdoc(7) */ case REQ_Nx: /* mdoc(7) */ case REQ_Ox: /* mdoc(7) */ case REQ_Bx: /* mdoc(7) */ case REQ_Ux: /* mdoc(7) */ case REQ_Dx: /* mdoc(7) */ { bool parsable=true; trans_char(c,'"','\a'); c=c+j; if (*c=='\n') c++; if (request==REQ_At) { out_html("AT&T UNIX "); parsable=false; } else if (request==REQ_Fx) { out_html("FreeBSD "); parsable=false; } else if (request==REQ_Nx) out_html("NetBSD "); else if (request==REQ_Ox) out_html("OpenBSD "); else if (request==REQ_Bx) out_html("BSD "); else if (request==REQ_Ux) out_html("UNIX "); else if (request==REQ_Dx) out_html("DragonFly "); if (parsable) c=scan_troff_mandoc(c,1,0); else c=scan_troff(c,1,0); if (fillout) curpos++; else curpos=0; break; } case REQ_Dl: /* mdoc(7) */ { c=c+j; out_html(NEWLINE); out_html("<BLOCKQUOTE>"); if (*c=='\n') c++; c=scan_troff_mandoc(c, 1, NULL); out_html("</BLOCKQUOTE>"); if (fillout) curpos++; else curpos=0; break; } case REQ_Bd: /* mdoc(7) */ { /* Seems like a kind of example/literal mode */ char bd_options[NULL_TERMINATED(MED_STR_MAX)]; char *nl = strchr(c,'\n'); c=c+j; if (nl) strlimitcpy(bd_options, c, nl - c, MED_STR_MAX); out_html(NEWLINE); mandoc_bd_options = 0; /* Remember options for terminating Bl */ if (strstr(bd_options, "-offset indent")) { mandoc_bd_options |= BD_INDENT; out_html("<BLOCKQUOTE>\n"); } if ( strstr(bd_options, "-literal") || strstr(bd_options, "-unfilled")) { if (fillout) { mandoc_bd_options |= BD_LITERAL; out_html(set_font("R")); out_html(change_to_size('0')); out_html("<PRE>\n"); } curpos=0; fillout=0; } c=skip_till_newline(c); break; } case REQ_Ed: /* mdoc(7) */ { if (mandoc_bd_options & BD_LITERAL) { if (!fillout) { out_html(set_font("R")); out_html(change_to_size('0')); out_html("</PRE>\n"); } } if (mandoc_bd_options & BD_INDENT) out_html("</BLOCKQUOTE>\n"); curpos=0; fillout=1; c=skip_till_newline(c); break; } case REQ_Be: /* mdoc(7) */ { c=c+j; if (fillout) out_html("<br><br>"); else { out_html(NEWLINE); } curpos=0; c=skip_till_newline(c); break; } case REQ_Xr: /* mdoc(7) */ // ### FIXME: it should issue a <a href="man:somewhere(x)"> directly { /* Translate xyz 1 to xyz(1) * Allow for multiple spaces. Allow the section to be missing. */ char buff[NULL_TERMINATED(MED_STR_MAX)]; char *bufptr; trans_char(c,'"','\a'); bufptr = buff; c = c+j; if (*c == '\n') c++; /* Skip spaces */ while (isspace(*c) && *c != '\n') c++; while (isalnum(*c) || *c == '.' || *c == ':' || *c == '_' || *c == '-') { /* Copy the xyz part */ *bufptr = *c; bufptr++; if (bufptr >= buff + MED_STR_MAX) break; c++; } while (isspace(*c) && *c != '\n') c++; /* Skip spaces */ if (isdigit(*c)) { /* Convert the number if there is one */ *bufptr = '('; bufptr++; if (bufptr < buff + MED_STR_MAX) { while (isalnum(*c)) { *bufptr = *c; bufptr++; if (bufptr >= buff + MED_STR_MAX) break; c++; } if (bufptr < buff + MED_STR_MAX) { *bufptr = ')'; bufptr++; } } } while (*c != '\n') { /* Copy the remainder */ if (!isspace(*c)) { *bufptr = *c; bufptr++; if (bufptr >= buff + MED_STR_MAX) break; } c++; } *bufptr = '\n'; bufptr[1] = 0; scan_troff_mandoc(buff, 1, NULL); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_Fl: // mdoc(7) "FLags" { trans_char(c,'"','\a'); c+=j; sl=fill_words(c, wordlist, &words, true, &c); out_html(set_font("B")); if (!words) { out_html("-"); // stdin or stdout } else { for (i=0;i<words;++i) { if (ispunct(wordlist[i][0]) && wordlist[i][0]!='-') { scan_troff_mandoc(wordlist[i], 1, NULL); } else { if (i>0) out_html(" "); // Put a space between flags out_html("-"); scan_troff_mandoc(wordlist[i], 1, NULL); } } } out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_Pa: /* mdoc(7) */ case REQ_Pf: /* mdoc(7) */ { trans_char(c,'"','\a'); c=c+j; if (*c=='\n') c++; c=scan_troff_mandoc(c, 1, NULL); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_Pp: /* mdoc(7) */ { if (fillout) out_html("<br><br>\n"); else { out_html(NEWLINE); } curpos=0; c=skip_till_newline(c); break; } case REQ_Aq: // mdoc(7) "Angle bracket Quote" c=process_quote(c,j,"<",">"); break; case REQ_Bq: // mdoc(7) "Bracket Quote" c=process_quote(c,j,"[","]"); break; case REQ_Dq: // mdoc(7) "Double Quote" c=process_quote(c,j,"“","”"); break; case REQ_Pq: // mdoc(7) "Parenthese Quote" c=process_quote(c,j,"(",")"); break; case REQ_Qq: // mdoc(7) "straight double Quote" c=process_quote(c,j,""","""); break; case REQ_Sq: // mdoc(7) "Single Quote" c=process_quote(c,j,"‘","’"); break; case REQ_Op: /* mdoc(7) */ { trans_char(c,'"','\a'); c=c+j; if (*c=='\n') c++; out_html(set_font("R")); out_html("["); c=scan_troff_mandoc(c, 1, NULL); out_html(set_font("R")); out_html("]"); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_Oo: /* mdoc(7) */ { trans_char(c,'"','\a'); c=c+j; if (*c=='\n') c++; out_html(set_font("R")); out_html("["); c=scan_troff_mandoc(c, 1, NULL); if (fillout) curpos++; else curpos=0; break; } case REQ_Oc: /* mdoc(7) */ { trans_char(c,'"','\a'); c=c+j; c=scan_troff_mandoc(c, 1, NULL); out_html(set_font("R")); out_html("]"); if (fillout) curpos++; else curpos=0; break; } case REQ_Ql: /* mdoc(7) */ { /* Single quote first word in the line */ char *sp; trans_char(c,'"','\a'); c=c+j; if (*c=='\n') c++; sp = c; do { /* Find first whitespace after the * first word that isn't a mandoc macro */ while (*sp && isspace(*sp)) sp++; while (*sp && !isspace(*sp)) sp++; } while (*sp && isupper(*(sp-2)) && islower(*(sp-1))); /* Use a newline to mark the end of text to * be quoted */ if (*sp) *sp = '\n'; out_html("`"); /* Quote the text */ c=scan_troff_mandoc(c, 1, NULL); out_html("'"); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_Ar: /* mdoc(7) */ { /* parse one line in italics */ out_html(set_font("I")); trans_char(c,'"','\a'); c=c+j; if (*c=='\n') { /* An empty Ar means "file ..." */ out_html("file ..."); } else c=scan_troff_mandoc(c, 1, NULL); out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_Em: /* mdoc(7) */ { out_html("<em>"); trans_char(c,'"','\a'); c+=j; if (*c=='\n') c++; c=scan_troff_mandoc(c, 1, NULL); out_html("</em>"); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_Ad: /* mdoc(7) */ case REQ_Va: /* mdoc(7) */ case REQ_Xc: /* mdoc(7) */ { /* parse one line in italics */ out_html(set_font("I")); trans_char(c,'"','\a'); c=c+j; if (*c=='\n') c++; c=scan_troff_mandoc(c, 1, NULL); out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_Nd: /* mdoc(7) */ { trans_char(c,'"','\a'); c=c+j; if (*c=='\n') c++; out_html(" - "); c=scan_troff_mandoc(c, 1, NULL); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_Nm: // mdoc(7) "Name Macro" ### FIXME { static char mandoc_name[NULL_TERMINATED(SMALL_STR_MAX)] = ""; // ### TODO Use QCString trans_char(c,'"','\a'); c=c+j; if (mandoc_synopsis && mandoc_name_count) { /* Break lines only in the Synopsis. * The Synopsis section seems to be treated * as a special case - Bummer! */ out_html("<BR>"); } else if (!mandoc_name_count) { char *nextbreak = strchr(c, '\n'); char *nextspace = strchr(c, ' '); if (nextspace < nextbreak) nextbreak = nextspace; if (nextbreak) { /* Remember the name for later. */ strlimitcpy(mandoc_name, c, nextbreak - c, SMALL_STR_MAX); } } mandoc_name_count++; out_html(set_font("B")); // ### FIXME: fill_words must be used while (*c == ' '|| *c == '\t') c++; if ((tolower(*c) >= 'a' && tolower(*c) <= 'z' ) || (*c >= '0' && *c <= '9')) { // alphanumeric argument c=scan_troff_mandoc(c, 1, NULL); out_html(set_font("R")); out_html(NEWLINE); } else { /* If Nm has no argument, use one from an earlier * Nm command that did have one. Hope there aren't * too many commands that do this. */ out_html(mandoc_name); out_html(set_font("R")); } if (fillout) curpos++; else curpos=0; break; } case REQ_Cd: /* mdoc(7) */ case REQ_Cm: /* mdoc(7) */ case REQ_Ic: /* mdoc(7) */ case REQ_Ms: /* mdoc(7) */ case REQ_Or: /* mdoc(7) */ case REQ_Sy: /* mdoc(7) */ { /* parse one line in bold */ out_html(set_font("B")); trans_char(c,'"','\a'); c=c+j; if (*c=='\n') c++; c=scan_troff_mandoc(c, 1, NULL); out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } // ### FIXME: punctuation is handled badly! case REQ_Dv: /* mdoc(7) */ case REQ_Ev: /* mdoc(7) */ case REQ_Fr: /* mdoc(7) */ case REQ_Li: /* mdoc(7) */ case REQ_No: /* mdoc(7) */ case REQ_Ns: /* mdoc(7) */ case REQ_Tn: /* mdoc(7) */ case REQ_nN: /* mdoc(7) */ { trans_char(c,'"','\a'); c=c+j; if (*c=='\n') c++; out_html(set_font("B")); c=scan_troff_mandoc(c, 1, NULL); out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; break; } case REQ_perc_A: /* mdoc(7) biblio stuff */ case REQ_perc_D: case REQ_perc_N: case REQ_perc_O: case REQ_perc_P: case REQ_perc_Q: case REQ_perc_V: { c=c+j; if (*c=='\n') c++; c=scan_troff(c, 1, NULL); /* Don't allow embedded mandoc coms */ if (fillout) curpos++; else curpos=0; break; } case REQ_perc_B: case REQ_perc_J: case REQ_perc_R: case REQ_perc_T: { c=c+j; out_html(set_font("I")); if (*c=='\n') c++; c=scan_troff(c, 1, NULL); /* Don't allow embedded mandoc coms */ out_html(set_font("R")); if (fillout) curpos++; else curpos=0; break; } case REQ_UR: // ### FIXME man(7) "URl" { ignore_links=true; c+=j; char* newc; h=fill_words(c, wordlist, &words, false, &newc); *h=0; if (words>0) { h=wordlist[0]; // A parameter : means that we do not want an URL, not here and not until .UE ur_ignore=(!qstrcmp(h,":")); } else { // We cannot find the URL, assume : ur_ignore=true; h=0; } if (!ur_ignore && words>0) { out_html("<a href=\""); out_html(h); out_html("\">"); } c=newc; // Go to next line break; } case REQ_UE: // ### FIXME man(7) "Url End" { c+=j; c = skip_till_newline(c); if (!ur_ignore) { out_html("</a>"); } ur_ignore=false; ignore_links=false; break; } case REQ_UN: // ### FIXME man(7) "Url Named anchor" { c+=j; char* newc; h=fill_words(c, wordlist, &words, false, &newc); *h=0; if (words>0) { h=wordlist[0]; out_html("<a name=\">"); out_html(h); out_html("\" id=\""); out_html(h); out_html("\"></a>"); } c=newc; break; } case REQ_nroff: // groff(7) "NROFF mode" mode = true; case REQ_troff: // groff(7) "TROFF mode" { s_nroff = mode; c+=j; c = skip_till_newline(c); } case REQ_als: // groff(7) "ALias String" { /* * Note an alias is supposed to be something like a hard link * However to make it simplier, we only copy the string. */ // Be careful: unlike .rn, the destination is first, origin is second kdDebug(7107) << "start .als" << endl; c+=j; const TQCString name ( scan_identifier( c ) ); if ( name.isEmpty() ) { kdDebug(7107) << "EXCEPTION: empty destination string to alias" << endl; break; } while (*c && isspace(*c) && *c!='\n') ++c; const TQCString name2 ( scan_identifier ( c ) ); if ( name2.isEmpty() ) { kdDebug(7107) << "EXCEPTION: empty origin string to alias" << endl; break; } kdDebug(7107) << "Alias " << name2 << " to " << name << endl; c=skip_till_newline(c); if ( name == name2 ) { kdDebug(7107) << "EXCEPTION: same origin and destination string to alias: " << name << endl; break; } // Second parametr is origin (unlike in .rn) TQMap<TQCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name2); if (it==s_stringDefinitionMap.end()) { kdDebug(7107) << "EXCEPTION: cannot find string to make alias: " << name2 << endl; } else { StringDefinition def=(*it); s_stringDefinitionMap.insert(name,def); } kdDebug(7107) << "end .als" << endl; break; } case REQ_rr: // groff(7) "Remove number Register" { kdDebug(7107) << "start .rr" << endl; c += j; const TQCString name ( scan_identifier( c ) ); if ( name.isEmpty() ) { kdDebug(7107) << "EXCEPTION: empty origin string to remove/rename: " << endl; break; } c = skip_till_newline( c ); TQMap <TQCString, NumberDefinition>::iterator it = s_numberDefinitionMap.find( name ); if ( it == s_numberDefinitionMap.end() ) { kdDebug(7107) << "EXCEPTION: trying to remove inexistant number register: " << endl; } else { s_numberDefinitionMap.remove( name ); } kdDebug(7107) << "end .rr" << endl; break; } case REQ_rnn: // groff(7) "ReName Number register" { kdDebug(7107) << "start .rnn" << endl; c+=j; const TQCString name ( scan_identifier ( c ) ); if ( name.isEmpty() ) { kdDebug(7107) << "EXCEPTION: empty origin to remove/rename number register" << endl; break; } while (*c && isspace(*c) && *c!='\n') ++c; const TQCString name2 ( scan_identifier ( c ) ); if ( name2.isEmpty() ) { kdDebug(7107) << "EXCEPTION: empty destination to rename number register " << endl; break; } c = skip_till_newline( c ); TQMap<TQCString,NumberDefinition>::iterator it=s_numberDefinitionMap.find(name); if (it==s_numberDefinitionMap.end()) { kdDebug(7107) << "EXCEPTION: cannot find number register to rename: " << name << endl; } else { NumberDefinition def=(*it); s_numberDefinitionMap.remove(name); // ### QT4: removeAll s_numberDefinitionMap.insert(name2,def); } kdDebug(7107) << "end .rnn" << endl; break; } case REQ_aln: // groff(7) "ALias Number Register" { /* * Note an alias is supposed to be something like a hard link * However to make it simplier, we only copy the string. */ // Be careful: unlike .rnn, the destination is first, origin is second kdDebug(7107) << "start .aln" << endl; c+=j; const TQCString name ( scan_identifier( c ) ); if ( name.isEmpty() ) { kdDebug(7107) << "EXCEPTION: empty destination number register to alias" << endl; break; } while (*c && isspace(*c) && *c!='\n') ++c; const TQCString name2 ( scan_identifier( c ) ); if ( name2.isEmpty() ) { kdDebug(7107) << "EXCEPTION: empty origin number register to alias" << endl; break; } kdDebug(7107) << "Alias " << name2 << " to " << name << endl; c = skip_till_newline( c ); if ( name == name2 ) { kdDebug(7107) << "EXCEPTION: same origin and destination number register to alias: " << name << endl; break; } // Second parametr is origin (unlike in .rnn) TQMap<TQCString,NumberDefinition>::iterator it=s_numberDefinitionMap.find(name2); if (it==s_numberDefinitionMap.end()) { kdDebug(7107) << "EXCEPTION: cannot find string to make alias: " << name2 << endl; } else { NumberDefinition def=(*it); s_numberDefinitionMap.insert(name,def); } kdDebug(7107) << "end .aln" << endl; break; } case REQ_shift: // groff(7) "SHIFT parameter" { c+=j; h=c; while (*h && *h!='\n' && isdigit(*h) ) ++h; const char tempchar = *h; *h = 0; const TQCString number = c; *h = tempchar; c = skip_till_newline( h ); unsigned int result = 1; // Numbers of shifts to do if ( !number.isEmpty() ) { bool ok = false; result = number.toUInt(&ok); if ( !ok || result < 1 ) result = 1; } for ( unsigned int num = 0; num < result; ++num ) { if ( !s_argumentList.isEmpty() ) s_argumentList.pop_front(); } break; } case REQ_while: // groff(7) "WHILE loop" { request_while( c, j, mandoc_command ); break; } case REQ_do: // groff(7) "DO command" { // HACK: we just replace do by a \n and a . *c = '\n'; c++; *c = '.'; // The . will be treated as next character break; } default: { if (mandoc_command && ((isupper(*c) && islower(*(c+1))) || (islower(*c) && isupper(*(c+1)))) ) { /* Let through any mdoc(7) commands that haven't * been delt with. * I don't want to miss anything out of the text. */ char buf[4] = { c[0], c[1], ' ', 0 }; out_html(buf); /* Print the command (it might just be text). */ c=c+j; trans_char(c,'"','\a'); if (*c=='\n') c++; out_html(set_font("R")); c=scan_troff(c, 1, NULL); out_html(NEWLINE); if (fillout) curpos++; else curpos=0; } else c=skip_till_newline(c); break; } } } } if (fillout) { out_html(NEWLINE); curpos++; } return c; } static int contained_tab=0; static bool mandoc_line=false; /* Signals whether to look for embedded mandoc * commands. */ static char *scan_troff(char *c, bool san, char **result) { /* san : stop at newline */ char *h; char intbuff[NULL_TERMINATED(MED_STR_MAX)]; int ibp=0; #define FLUSHIBP if (ibp) { intbuff[ibp]=0; out_html(intbuff); ibp=0; } char *exbuffer; int exbuffpos, exbuffmax, exnewline_for_fun; bool exscaninbuff; int usenbsp=0; exbuffer=buffer; exbuffpos=buffpos; exbuffmax=buffmax; exnewline_for_fun=newline_for_fun; exscaninbuff=scaninbuff; newline_for_fun=0; if (result) { if (*result) { buffer=*result; buffpos=tqstrlen(buffer); buffmax=buffpos; } else { buffer = stralloc(LARGE_STR_MAX); buffpos=0; buffmax=LARGE_STR_MAX; } scaninbuff=true; } h=c; // ### FIXME below are too many tests that may go before the posiiton of c /* start scanning */ // ### VERIFY: a dot must be at first position, we cannot add newlines or it would allow spaces before a dot while (*h == ' ') { #if 1 ++h; #else *h++ = '\n'; #endif } while (h && *h && (!san || newline_for_fun || *h!='\n')) { if (*h==escapesym) { h++; FLUSHIBP; h = scan_escape(h); } else if (*h==controlsym && h[-1]=='\n') { h++; FLUSHIBP; h = scan_request(h); if (h && san && h[-1]=='\n') h--; } else if (mandoc_line && ((*(h-1)) && (isspace(*(h-1)) || (*(h-1))=='\n')) && *(h) && isupper(*(h)) && *(h+1) && islower(*(h+1)) && *(h+2) && isspace(*(h+2))) { // mdoc(7) embedded command eg ".It Fl Ar arg1 Fl Ar arg2" FLUSHIBP; h = scan_request(h); if (san && h[-1]=='\n') h--; } else if (*h==nobreaksym && h[-1]=='\n') { h++; FLUSHIBP; h = scan_request(h); if (san && h[-1]=='\n') h--; } else { /* int mx; */ if (still_dd && isalnum(*h) && h[-1]=='\n') { /* sometimes a .HP request is not followed by a .br request */ FLUSHIBP; out_html("<DD>"); curpos=0; still_dd=false; } switch (*h) { case '&': intbuff[ibp++]='&'; intbuff[ibp++]='a'; intbuff[ibp++]='m'; intbuff[ibp++]='p'; intbuff[ibp++]=';'; curpos++; break; case '<': intbuff[ibp++]='&'; intbuff[ibp++]='l'; intbuff[ibp++]='t'; intbuff[ibp++]=';'; curpos++; break; case '>': intbuff[ibp++]='&'; intbuff[ibp++]='g'; intbuff[ibp++]='t'; intbuff[ibp++]=';'; curpos++; break; case '"': intbuff[ibp++]='&'; intbuff[ibp++]='q'; intbuff[ibp++]='u'; intbuff[ibp++]='o'; intbuff[ibp++]='t'; intbuff[ibp++]=';'; curpos++; break; case '\n': if (h != c && h[-1]=='\n' && fillout) { intbuff[ibp++]='<'; intbuff[ibp++]='P'; intbuff[ibp++]='>'; } if (contained_tab && fillout) { intbuff[ibp++]='<'; intbuff[ibp++]='B'; intbuff[ibp++]='R'; intbuff[ibp++]='>'; } contained_tab=0; curpos=0; usenbsp=0; intbuff[ibp++]='\n'; break; case '\t': { int curtab=0; contained_tab=1; FLUSHIBP; /* like a typewriter, not like TeX */ tabstops[19]=curpos+1; while (curtab<maxtstop && tabstops[curtab]<=curpos) curtab++; if (curtab<maxtstop) { if (!fillout) { while (curpos<tabstops[curtab]) { intbuff[ibp++]=' '; if (ibp>480) { FLUSHIBP; } curpos++; } } else { out_html("<TT>"); while (curpos<tabstops[curtab]) { out_html(" "); curpos++; } out_html("</TT>"); } } } break; default: if (*h==' ' && (h[-1]=='\n' || usenbsp)) { FLUSHIBP; if (!usenbsp && fillout) { out_html("<BR>"); curpos=0; } usenbsp=fillout; if (usenbsp) out_html(" "); else intbuff[ibp++]=' '; } else if (*h>31 && *h<127) intbuff[ibp++]=*h; else if (((unsigned char)(*h))>127) { intbuff[ibp++]=*h; } curpos++; break; } if (ibp > (MED_STR_MAX - 20)) FLUSHIBP; h++; } } FLUSHIBP; if (buffer) buffer[buffpos]='\0'; if (san && h && *h) h++; newline_for_fun=exnewline_for_fun; if (result) { *result = buffer; buffer=exbuffer; buffpos=exbuffpos; buffmax=exbuffmax; scaninbuff=exscaninbuff; } return h; } static char *scan_troff_mandoc(char *c, bool san, char **result) { char *ret; char *end = c; bool oldval = mandoc_line; mandoc_line = true; while (*end && *end != '\n') { end++; } if (end > c + 2 && ispunct(*(end - 1)) && isspace(*(end - 2)) && *(end - 2) != '\n') { /* Don't format lonely punctuation E.g. in "xyz ," format * the xyz and then append the comma removing the space. */ *(end - 2) = '\n'; ret = scan_troff(c, san, result); *(end - 2) = *(end - 1); *(end - 1) = ' '; } else { ret = scan_troff(c, san, result); } mandoc_line = oldval; return ret; } // Entry point void scan_man_page(const char *man_page) { if (!man_page) return; kdDebug(7107) << "Start scanning man page" << endl; // ## Do more init // Unlike man2html, we actually call this several times, hence the need to // properly cleanup all those static vars s_ifelseval.clear(); s_characterDefinitionMap.clear(); InitCharacterDefinitions(); s_stringDefinitionMap.clear(); InitStringDefinitions(); s_numberDefinitionMap.clear(); InitNumberDefinitions(); s_argumentList.clear(); section = 0; s_dollarZero = ""; // No macro called yet! output_possible = false; int strLength = tqstrlen(man_page); char *buf = new char[strLength + 2]; qstrcpy(buf+1, man_page); buf[0] = '\n'; kdDebug(7107) << "Parse man page" << endl; scan_troff(buf+1,0,NULL); kdDebug(7107) << "Man page parsed!" << endl; while (itemdepth || dl_set[itemdepth]) { out_html("</DL>\n"); if (dl_set[itemdepth]) dl_set[itemdepth]=0; else if (itemdepth > 0) itemdepth--; } out_html(set_font("R")); out_html(change_to_size(0)); if (!fillout) { fillout=1; out_html("</PRE>"); } out_html(NEWLINE); if (section) { output_real("<div style=\"margin-left: 2cm\">\n"); section = 0; } if (output_possible) { output_real("</div>\n"); output_real("<div class=\"bannerBottom\" style=\"background-image: url("); output_real(cssPath); output_real("/bottom-middle.png); background-repeat: x-repeat; width: 100%; height: 100px; bottom:0pt;\">\n"); output_real("<div class=\"bannerBottomLeft\">\n"); output_real("<img src=\""); output_real(cssPath); output_real("/bottom-left.png\" style=\"margin: 0pt;\" alt=\"Bottom left of the banner\">\n"); output_real("</div>\n"); output_real("<div class=\"bannerBottomRight\">\n"); output_real("<img src=\""); output_real(cssPath); output_real("/bottom-right.png\" style=\"margin: 0pt\" alt=\"Bottom right of the banner\">\n"); output_real("</div>\n"); output_real("</div>\n"); output_real("</BODY>\n</HTML>\n"); } delete [] buf; // Release memory s_characterDefinitionMap.clear(); s_stringDefinitionMap.clear(); s_numberDefinitionMap.clear(); s_argumentList.clear(); // reinit static variables for reuse delete [] buffer; buffer = 0; escapesym='\\'; nobreaksym='\''; controlsym='.'; fieldsym=0; padsym=0; buffpos=0; buffmax=0; scaninbuff=false; itemdepth=0; for (int i = 0; i < 20; i++) dl_set[i] = 0; still_dd=false; for (int i = 0; i < 12; i++) tabstops[i] = (i+1)*8; maxtstop=12; curpos=0; mandoc_name_count = 0; } #ifdef SIMPLE_MAN2HTML void output_real(const char *insert) { cout << insert; } char *read_man_page(const char *filename) { int man_pipe = 0; char *man_buf = NULL; FILE *man_stream = NULL; struct stat stbuf; size_t buf_size; if (stat(filename, &stbuf) == -1) { std::cerr << "read_man_page: can't find " << filename << endl; return NULL; } if (!S_ISREG(stbuf.st_mode)) { std::cerr << "read_man_page: no file " << filename << endl; return NULL; } buf_size = stbuf.st_size; man_buf = stralloc(buf_size+5); man_pipe = 0; man_stream = fopen(filename, "r"); if (man_stream) { man_buf[0] = '\n'; if (fread(man_buf+1, 1, buf_size, man_stream) == buf_size) { man_buf[buf_size] = '\n'; man_buf[buf_size + 1] = man_buf[buf_size + 2] = '\0'; } else { man_buf = NULL; } fclose(man_stream); } return man_buf; } int main(int argc, char **argv) { htmlPath = "."; cssPath = "."; if (argc < 2) { std::cerr << "call: " << argv[0] << " <filename>\n"; return 1; } if (chdir(argv[1])) { char *buf = read_man_page(argv[1]); if (buf) { scan_man_page(buf); delete [] buf; } } else { DIR *dir = opendir("."); struct dirent *ent; while ((ent = readdir(dir)) != NULL) { cerr << "converting " << ent->d_name << endl; char *buf = read_man_page(ent->d_name); if (buf) { scan_man_page(buf); delete [] buf; } } closedir(dir); } return 0; } #endif