diff options
Diffstat (limited to 'tdeioslave/man/man2html.cpp')
-rw-r--r-- | tdeioslave/man/man2html.cpp | 5685 |
1 files changed, 5685 insertions, 0 deletions
diff --git a/tdeioslave/man/man2html.cpp b/tdeioslave/man/man2html.cpp new file mode 100644 index 000000000..ec46b2d3d --- /dev/null +++ b/tdeioslave/man/man2html.cpp @@ -0,0 +1,5685 @@ +/* + 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 |