summaryrefslogtreecommitdiffstats
path: root/kioslave/man
diff options
context:
space:
mode:
authortoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
committertoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
commit4aed2c8219774f5d797760606b8489a92ddc5163 (patch)
tree3f8c130f7d269626bf6a9447407ef6c35954426a /kioslave/man
downloadtdebase-4aed2c8219774f5d797760606b8489a92ddc5163.tar.gz
tdebase-4aed2c8219774f5d797760606b8489a92ddc5163.zip
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdebase@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kioslave/man')
-rw-r--r--kioslave/man/LICENSE16
-rw-r--r--kioslave/man/Makefile.am51
-rw-r--r--kioslave/man/kio_man.cpp1532
-rw-r--r--kioslave/man/kio_man.css21
-rw-r--r--kioslave/man/kio_man.h99
-rw-r--r--kioslave/man/kio_man_test.cpp38
-rw-r--r--kioslave/man/kmanpart.cpp115
-rw-r--r--kioslave/man/kmanpart.desktop91
-rw-r--r--kioslave/man/kmanpart.h79
-rw-r--r--kioslave/man/man.protocol12
-rw-r--r--kioslave/man/man2html.cpp5683
-rw-r--r--kioslave/man/man2html.h34
12 files changed, 7771 insertions, 0 deletions
diff --git a/kioslave/man/LICENSE b/kioslave/man/LICENSE
new file mode 100644
index 000000000..d28a48f92
--- /dev/null
+++ b/kioslave/man/LICENSE
@@ -0,0 +1,16 @@
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/kioslave/man/Makefile.am b/kioslave/man/Makefile.am
new file mode 100644
index 000000000..365d2f774
--- /dev/null
+++ b/kioslave/man/Makefile.am
@@ -0,0 +1,51 @@
+## Makefile.am of kdebase/kioslave/man
+
+INCLUDES= $(all_includes)
+AM_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+
+EXTRA_PROGRAMS = kio_man_test man2html
+
+####### just for testing (j.habenicht@europemail.com, 15.02.2001)
+
+kio_man_test_SOURCES = kio_man_test.cpp
+kio_man_test_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+kio_man_test_LDADD = man2html.lo kio_man.lo $(LIB_KIO) $(LIB_KDEUI) $(LIB_KDECORE) $(LIB_QT)
+
+####### Files
+
+kde_module_LTLIBRARIES = kio_man.la libkmanpart.la
+
+kio_man_la_SOURCES = man2html.cpp kio_man.cpp
+kio_man_la_LIBADD = $(LIB_KSYCOCA)
+kio_man_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+noinst_HEADERS = kio_man.h
+### TODO Why is man2htmk.h distributed?
+
+libkmanpart_la_SOURCES = kmanpart.cpp
+libkmanpart_la_LIBADD = -lkhtml $(LIB_KPARTS)
+libkmanpart_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN)
+
+kdelnk_DATA = man.protocol kmanpart.desktop
+kdelnkdir = $(kde_servicesdir)
+
+kio_man_data_DATA = kio_man.css
+kio_man_datadir = $(kde_datadir)/kio_man
+EXTRA_DIST=$(kio_man_data_DATA)
+
+METASOURCES = AUTO
+
+messages:
+ $(XGETTEXT) *.cpp *.h -o $(podir)/kio_man.pot
+
+man2html_SOURCES = dummy.cpp
+man2html_LDADD = man2html_simple.o $(LIB_QT)
+man2html_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+
+dummy.cpp:
+ echo > $@
+
+man2html_simple.o: $(srcdir)/man2html.cpp
+ -rm -f man2html_simple.cpp
+ $(LN_S) $(srcdir)/man2html.cpp man2html_simple.cpp
+ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) -DSIMPLE_MAN2HTML $(INCLUDES) $(CPPFLAGS) $(CXXFLAGS) -c man2html_simple.cpp
+
diff --git a/kioslave/man/kio_man.cpp b/kioslave/man/kio_man.cpp
new file mode 100644
index 000000000..0511a165d
--- /dev/null
+++ b/kioslave/man/kio_man.cpp
@@ -0,0 +1,1532 @@
+/* This file is part of the KDE libraries
+ Copyright (c) 2000 Matthias Hoelzer-Kluepfel <mhk@caldera.de>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <dirent.h>
+
+#include <qdir.h>
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qdatastream.h>
+#include <qcstring.h>
+#include <qptrlist.h>
+#include <qmap.h>
+#include <qregexp.h>
+
+#include <kdebug.h>
+#include <kinstance.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+#include <kprocess.h>
+#include <klocale.h>
+#include <kmimetype.h>
+
+#include "kio_man.h"
+#include "kio_man.moc"
+#include "man2html.h"
+#include <assert.h>
+#include <kfilterbase.h>
+#include <kfilterdev.h>
+
+using namespace KIO;
+
+MANProtocol *MANProtocol::_self = 0;
+
+#define SGML2ROFF_DIRS "/usr/lib/sgml"
+
+/*
+ * Drop trailing ".section[.gz]" from name
+ */
+static
+void stripExtension( QString *name )
+{
+ int pos = name->length();
+
+ if ( name->find(".gz", -3) != -1 )
+ pos -= 3;
+ else if ( name->find(".z", -2, false) != -1 )
+ pos -= 2;
+ else if ( name->find(".bz2", -4) != -1 )
+ pos -= 4;
+ else if ( name->find(".bz", -3) != -1 )
+ pos -= 3;
+
+ if ( pos > 0 )
+ pos = name->findRev('.', pos-1);
+
+ if ( pos > 0 )
+ name->truncate( pos );
+}
+
+static
+bool parseUrl(const QString& _url, QString &title, QString &section)
+{
+ section = QString::null;
+
+ QString url = _url;
+ if (url.at(0) == '/') {
+ if (KStandardDirs::exists(url)) {
+ title = url;
+ return true;
+ } else
+ {
+ // If the directory does not exist, then it is perhaps a normal man page
+ kdDebug(7107) << url << " does not exist" << endl;
+ }
+ }
+
+ while (url.at(0) == '/')
+ url.remove(0,1);
+
+ title = url;
+
+ int pos = url.find('(');
+ if (pos < 0)
+ return true;
+
+ title = title.left(pos);
+
+ section = url.mid(pos+1);
+ section = section.left(section.length()-1);
+
+ return true;
+}
+
+
+MANProtocol::MANProtocol(const QCString &pool_socket, const QCString &app_socket)
+ : QObject(), SlaveBase("man", pool_socket, app_socket)
+{
+ assert(!_self);
+ _self = this;
+ const QString common_dir = KGlobal::dirs()->findResourceDir( "html", "en/common/kde-common.css" );
+ const QString strPath=QString( "file:%1/en/common" ).arg( common_dir );
+ m_htmlPath=strPath.local8Bit(); // ### TODO encode for HTML
+ m_cssPath=strPath.local8Bit(); // ### TODO encode for CSS
+ section_names << "1" << "2" << "3" << "3n" << "3p" << "4" << "5" << "6" << "7"
+ << "8" << "9" << "l" << "n";
+ m_manCSSFile = locate( "data", "kio_man/kio_man.css" );
+}
+
+MANProtocol *MANProtocol::self() { return _self; }
+
+MANProtocol::~MANProtocol()
+{
+ _self = 0;
+}
+
+void MANProtocol::parseWhatIs( QMap<QString, QString> &i, QTextStream &t, const QString &mark )
+{
+ QRegExp re( mark );
+ QString l;
+ while ( !t.atEnd() )
+ {
+ l = t.readLine();
+ int pos = re.search( l );
+ if (pos != -1)
+ {
+ QString names = l.left(pos);
+ QString descr = l.mid(pos + re.matchedLength());
+ while ((pos = names.find(",")) != -1)
+ {
+ i[names.left(pos++)] = descr;
+ while (names[pos] == ' ')
+ pos++;
+ names = names.mid(pos);
+ }
+ i[names] = descr;
+ }
+ }
+}
+
+bool MANProtocol::addWhatIs(QMap<QString, QString> &i, const QString &name, const QString &mark)
+{
+ QFile f(name);
+ if (!f.open(IO_ReadOnly))
+ return false;
+ QTextStream t(&f);
+ parseWhatIs( i, t, mark );
+ return true;
+}
+
+QMap<QString, QString> MANProtocol::buildIndexMap(const QString &section)
+{
+ QMap<QString, QString> i;
+ QStringList man_dirs = manDirectories();
+ // Supplementary places for whatis databases
+ man_dirs += m_mandbpath;
+ if (man_dirs.find("/var/cache/man")==man_dirs.end())
+ man_dirs << "/var/cache/man";
+ if (man_dirs.find("/var/catman")==man_dirs.end())
+ man_dirs << "/var/catman";
+
+ QStringList names;
+ names << "whatis.db" << "whatis";
+ QString mark = "\\s+\\(" + section + "[a-z]*\\)\\s+-\\s+";
+
+ for ( QStringList::ConstIterator it_dir = man_dirs.begin();
+ it_dir != man_dirs.end();
+ ++it_dir )
+ {
+ if ( QFile::exists( *it_dir ) ) {
+ QStringList::ConstIterator it_name;
+ for ( it_name = names.begin();
+ it_name != names.end();
+ it_name++ )
+ {
+ if (addWhatIs(i, (*it_dir) + "/" + (*it_name), mark))
+ break;
+ }
+ if ( it_name == names.end() ) {
+ KProcess proc;
+ proc << "whatis" << "-M" << (*it_dir) << "-w" << "*";
+ myStdStream = QString::null;
+ connect( &proc, SIGNAL( receivedStdout(KProcess *, char *, int ) ),
+ SLOT( slotGetStdOutput( KProcess *, char *, int ) ) );
+ proc.start( KProcess::Block, KProcess::Stdout );
+ QTextStream t( &myStdStream, IO_ReadOnly );
+ parseWhatIs( i, t, mark );
+ }
+ }
+ }
+ return i;
+}
+
+QStringList MANProtocol::manDirectories()
+{
+ checkManPaths();
+ //
+ // Build a list of man directories including translations
+ //
+ QStringList man_dirs;
+
+ for ( QStringList::ConstIterator it_dir = m_manpath.begin();
+ it_dir != m_manpath.end();
+ it_dir++ )
+ {
+ // Translated pages in "<mandir>/<lang>" if the directory
+ // exists
+ QStringList languages = KGlobal::locale()->languageList();
+
+ for (QStringList::ConstIterator it_lang = languages.begin();
+ it_lang != languages.end();
+ it_lang++ )
+ {
+ if ( !(*it_lang).isEmpty() && (*it_lang) != QString("C") ) {
+ QString dir = (*it_dir) + '/' + (*it_lang);
+
+ struct stat sbuf;
+
+ if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0
+ && S_ISDIR( sbuf.st_mode ) )
+ {
+ const QString p = QDir(dir).canonicalPath();
+ if (!man_dirs.contains(p)) man_dirs += p;
+ }
+ }
+ }
+
+ // Untranslated pages in "<mandir>"
+ const QString p = QDir(*it_dir).canonicalPath();
+ if (!man_dirs.contains(p)) man_dirs += p;
+ }
+ return man_dirs;
+}
+
+QStringList MANProtocol::findPages(const QString &_section,
+ const QString &title,
+ bool full_path)
+{
+ QString section = _section;
+
+ QStringList list;
+
+ // kdDebug() << "findPages '" << section << "' '" << title << "'\n";
+ if (title.at(0) == '/') {
+ list.append(title);
+ return list;
+ }
+
+ const QString star( "*" );
+
+ //
+ // Find man sections in this directory
+ //
+ QStringList sect_list;
+ if ( section.isEmpty() )
+ section = star;
+
+ if ( section != star )
+ {
+ //
+ // Section given as argument
+ //
+ sect_list += section;
+ while (section.at(section.length() - 1).isLetter()) {
+ section.truncate(section.length() - 1);
+ sect_list += section;
+ }
+ } else {
+ sect_list += section;
+ }
+
+ QStringList man_dirs = manDirectories();
+
+ //
+ // Find man pages in the sections listed above
+ //
+ for ( QStringList::ConstIterator it_sect = sect_list.begin();
+ it_sect != sect_list.end();
+ it_sect++ )
+ {
+ QString it_real = (*it_sect).lower();
+ //
+ // Find pages
+ //
+ for ( QStringList::ConstIterator it_dir = man_dirs.begin();
+ it_dir != man_dirs.end();
+ it_dir++ )
+ {
+ QString man_dir = (*it_dir);
+
+ //
+ // Sections = all sub directories "man*" and "sman*"
+ //
+ DIR *dp = ::opendir( QFile::encodeName( man_dir ) );
+
+ if ( !dp )
+ continue;
+
+ struct dirent *ep;
+
+ const QString man = QString("man");
+ const QString sman = QString("sman");
+
+ while ( (ep = ::readdir( dp )) != 0L ) {
+ const QString file = QFile::decodeName( ep->d_name );
+ QString sect = QString::null;
+
+ if ( file.startsWith( man ) )
+ sect = file.mid(3);
+ else if (file.startsWith(sman))
+ sect = file.mid(4);
+
+ if (sect.lower()==it_real) it_real = sect;
+
+ // Only add sect if not already contained, avoid duplicates
+ if (!sect_list.contains(sect) && _section.isEmpty()) {
+ kdDebug() << "another section " << sect << endl;
+ sect_list += sect;
+ }
+ }
+
+ ::closedir( dp );
+
+ if ( *it_sect != star ) { // in that case we only look around for sections
+ const QString dir = man_dir + QString("/man") + (it_real) + '/';
+ const QString sdir = man_dir + QString("/sman") + (it_real) + '/';
+
+ findManPagesInSection(dir, title, full_path, list);
+ findManPagesInSection(sdir, title, full_path, list);
+ }
+ }
+ }
+
+// kdDebug(7107) << "finished " << list << " " << sect_list << endl;
+
+ return list;
+}
+
+void MANProtocol::findManPagesInSection(const QString &dir, const QString &title, bool full_path, QStringList &list)
+{
+ kdDebug() << "findManPagesInSection " << dir << " " << title << endl;
+ bool title_given = !title.isEmpty();
+
+ DIR *dp = ::opendir( QFile::encodeName( dir ) );
+
+ if ( !dp )
+ return;
+
+ struct dirent *ep;
+
+ while ( (ep = ::readdir( dp )) != 0L ) {
+ if ( ep->d_name[0] != '.' ) {
+
+ QString name = QFile::decodeName( ep->d_name );
+
+ // check title if we're looking for a specific page
+ if ( title_given ) {
+ if ( !name.startsWith( title ) ) {
+ continue;
+ }
+ else {
+ // beginning matches, do a more thorough check...
+ QString tmp_name = name;
+ stripExtension( &tmp_name );
+ if ( tmp_name != title )
+ continue;
+ }
+ }
+
+ if ( full_path )
+ name.prepend( dir );
+
+ list += name ;
+ }
+ }
+ ::closedir( dp );
+}
+
+void MANProtocol::output(const char *insert)
+{
+ if (insert)
+ {
+ m_outputBuffer.writeBlock(insert,strlen(insert));
+ }
+ if (!insert || m_outputBuffer.at() >= 2048)
+ {
+ m_outputBuffer.close();
+ data(m_outputBuffer.buffer());
+ m_outputBuffer.setBuffer(QByteArray());
+ m_outputBuffer.open(IO_WriteOnly);
+ }
+}
+
+// called by man2html
+char *read_man_page(const char *filename)
+{
+ return MANProtocol::self()->readManPage(filename);
+}
+
+// called by man2html
+void output_real(const char *insert)
+{
+ MANProtocol::self()->output(insert);
+}
+
+static QString text2html(const QString& txt)
+{
+ QString reply = txt;
+
+ reply = reply.replace('&', "&amp;");
+ reply = reply.replace('<', "&lt;");
+ reply = reply.replace('>', "&gt;");
+ reply = reply.replace('"', "&dquot;");
+ reply = reply.replace('\'', "&quot;");
+ return reply;
+}
+
+void MANProtocol::get(const KURL& url )
+{
+ kdDebug(7107) << "GET " << url.url() << endl;
+
+ QString title, section;
+
+ if (!parseUrl(url.path(), title, section))
+ {
+ showMainIndex();
+ return;
+ }
+
+ // see if an index was requested
+ if (url.query().isEmpty() && (title.isEmpty() || title == "/" || title == "."))
+ {
+ if (section == "index" || section.isEmpty())
+ showMainIndex();
+ else
+ showIndex(section);
+ return;
+ }
+
+ // tell the mimetype
+ mimeType("text/html");
+
+ const QStringList foundPages=findPages(section, title);
+ bool pageFound=true;
+ if (foundPages.isEmpty())
+ {
+ outputError(i18n("No man page matching to %1 found.<br><br>"
+ "Check that you have not mistyped the name of the page that you want.\n"
+ "Be careful that you must take care about upper case and lower case characters!<br>"
+ "If everything looks correct, then perhaps you need to set a better search path "
+ "for man pages, be it by the environment variable MANPATH or a matching file "
+ "in the directory /etc .").arg(text2html(title)));
+ pageFound=false;
+ }
+ else if (foundPages.count()>1)
+ {
+ pageFound=false;
+ //check for the case that there is foo.1 and foo.1.gz found:
+ // ### TODO make it more generic (other extensions)
+ if ((foundPages.count()==2) &&
+ (((foundPages[0]+".gz") == foundPages[1]) ||
+ (foundPages[0] == (foundPages[1]+".gz"))))
+ pageFound=true;
+ else
+ outputMatchingPages(foundPages);
+ }
+ //yes, we found exactly one man page
+
+ if (pageFound)
+ {
+ setResourcePath(m_htmlPath,m_cssPath);
+ m_outputBuffer.open(IO_WriteOnly);
+ const QCString filename=QFile::encodeName(foundPages[0]);
+ char *buf = readManPage(filename);
+
+ if (!buf)
+ {
+ outputError(i18n("Open of %1 failed.").arg(title));
+ finished();
+ return;
+ }
+ // will call output_real
+ scan_man_page(buf);
+ delete [] buf;
+
+ output(0); // flush
+
+ m_outputBuffer.close();
+ data(m_outputBuffer.buffer());
+ m_outputBuffer.setBuffer(QByteArray());
+ // tell we are done
+ data(QByteArray());
+ }
+ finished();
+}
+
+void MANProtocol::slotGetStdOutput(KProcess* /* p */, char *s, int len)
+{
+ myStdStream += QString::fromLocal8Bit(s, len);
+}
+
+char *MANProtocol::readManPage(const char *_filename)
+{
+ QCString filename = _filename;
+
+ char *buf = NULL;
+
+ /* Determine type of man page file by checking its path. Determination by
+ * MIME type with KMimeType doesn't work reliablely. E.g., Solaris 7:
+ * /usr/man/sman7fs/pcfs.7fs -> text/x-csrc : WRONG
+ * If the path name constains the string sman, assume that it's SGML and
+ * convert it to roff format (used on Solaris). */
+ //QString file_mimetype = KMimeType::findByPath(QString(filename), 0, false)->name();
+ if (filename.contains("sman", false)) //file_mimetype == "text/html" || )
+ {
+ myStdStream =QString::null;
+ KProcess proc;
+
+ /* Determine path to sgml2roff, if not already done. */
+ getProgramPath();
+ proc << mySgml2RoffPath << filename;
+
+ QApplication::connect(&proc, SIGNAL(receivedStdout (KProcess *, char *, int)),
+ this, SLOT(slotGetStdOutput(KProcess *, char *, int)));
+ proc.start(KProcess::Block, KProcess::All);
+
+ const QCString cstr=myStdStream.latin1();
+ const int len = cstr.size()-1;
+ buf = new char[len + 4];
+ qmemmove(buf + 1, cstr.data(), len);
+ buf[0]=buf[len]='\n'; // Start and end with a end of line
+ buf[len+1]=buf[len+2]='\0'; // Two additional NUL characters at end
+ }
+ else
+ {
+ if (QDir::isRelativePath(filename)) {
+ kdDebug(7107) << "relative " << filename << endl;
+ filename = QDir::cleanDirPath(lastdir + "/" + filename).utf8();
+ if (!KStandardDirs::exists(filename)) { // exists perhaps with suffix
+ lastdir = filename.left(filename.findRev('/'));
+ QDir mandir(lastdir);
+ mandir.setNameFilter(filename.mid(filename.findRev('/') + 1) + ".*");
+ filename = lastdir + "/" + QFile::encodeName(mandir.entryList().first());
+ }
+ kdDebug(7107) << "resolved to " << filename << endl;
+ }
+ lastdir = filename.left(filename.findRev('/'));
+
+ QIODevice *fd= KFilterDev::deviceForFile(filename);
+
+ if ( !fd || !fd->open(IO_ReadOnly))
+ {
+ delete fd;
+ return 0;
+ }
+ QByteArray array(fd->readAll());
+ kdDebug(7107) << "read " << array.size() << endl;
+ fd->close();
+ delete fd;
+
+ if (array.isEmpty())
+ return 0;
+
+ const int len = array.size();
+ buf = new char[len + 4];
+ qmemmove(buf + 1, array.data(), len);
+ buf[0]=buf[len]='\n'; // Start and end with a end of line
+ buf[len+1]=buf[len+2]='\0'; // Two NUL characters at end
+ }
+ return buf;
+}
+
+
+void MANProtocol::outputError(const QString& errmsg)
+{
+ QByteArray array;
+ QTextStream os(array, IO_WriteOnly);
+ os.setEncoding(QTextStream::UnicodeUTF8);
+
+ os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl;
+ os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl;
+ os << "<title>" << i18n("Man output") << "</title>\n" << endl;
+ if ( !m_manCSSFile.isEmpty() )
+ os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl;
+ os << "</head>" << endl;
+ os << i18n("<body><h1>KDE Man Viewer Error</h1>") << errmsg << "</body>" << endl;
+ os << "</html>" << endl;
+
+ data(array);
+}
+
+void MANProtocol::outputMatchingPages(const QStringList &matchingPages)
+{
+ QByteArray array;
+ QTextStream os(array, IO_WriteOnly);
+ os.setEncoding(QTextStream::UnicodeUTF8);
+
+ os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl;
+ os << "<html>\n<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"<<endl;
+ os << "<title>" << i18n("Man output") <<"</title>" << endl;
+ if ( !m_manCSSFile.isEmpty() )
+ os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl;
+ os << "</head>" <<endl;
+ os << "<body><h1>" << i18n("There is more than one matching man page.");
+ os << "</h1>\n<ul>\n";
+
+ int acckey=1;
+ for (QStringList::ConstIterator it = matchingPages.begin(); it != matchingPages.end(); ++it)
+ {
+ os<<"<li><a href='man:"<<(*it)<<"' accesskey='"<< acckey <<"'>"<< *it <<"</a><br>\n<br>\n";
+ acckey++;
+ }
+ os << "</ul>\n";
+ os << "<hr>\n";
+ os << "<p>" << i18n("Note: if you read a man page in your language,"
+ " be aware it can contain some mistakes or be obsolete."
+ " In case of doubt, you should have a look at the English version.") << "</p>";
+
+ os << "</body>\n</html>"<<endl;
+
+ data(array);
+ finished();
+}
+
+void MANProtocol::stat( const KURL& url)
+{
+ kdDebug(7107) << "ENTERING STAT " << url.url() << endl;
+
+ QString title, section;
+
+ if (!parseUrl(url.path(), title, section))
+ {
+ error(KIO::ERR_MALFORMED_URL, url.url());
+ return;
+ }
+
+ kdDebug(7107) << "URL " << url.url() << " parsed to title='" << title << "' section=" << section << endl;
+
+ UDSEntry entry;
+ UDSAtom atom;
+
+ atom.m_uds = UDS_NAME;
+ atom.m_long = 0;
+ atom.m_str = title;
+ entry.append(atom);
+
+ atom.m_uds = UDS_FILE_TYPE;
+ atom.m_str = "";
+ atom.m_long = S_IFREG;
+ entry.append(atom);
+
+ atom.m_uds = UDS_URL;
+ atom.m_long = 0;
+ QString newUrl = "man:"+title;
+ if (!section.isEmpty())
+ newUrl += QString("(%1)").arg(section);
+ atom.m_str = newUrl;
+ entry.append(atom);
+
+ atom.m_uds = UDS_MIME_TYPE;
+ atom.m_long = 0;
+ atom.m_str = "text/html";
+ entry.append(atom);
+
+ statEntry(entry);
+
+ finished();
+}
+
+
+extern "C"
+{
+
+ int KDE_EXPORT kdemain( int argc, char **argv ) {
+
+ KInstance instance("kio_man");
+
+ kdDebug(7107) << "STARTING " << getpid() << endl;
+
+ if (argc != 4)
+ {
+ fprintf(stderr, "Usage: kio_man protocol domain-socket1 domain-socket2\n");
+ exit(-1);
+ }
+
+ MANProtocol slave(argv[2], argv[3]);
+ slave.dispatchLoop();
+
+ kdDebug(7107) << "Done" << endl;
+
+ return 0;
+ }
+
+}
+
+void MANProtocol::mimetype(const KURL & /*url*/)
+{
+ mimeType("text/html");
+ finished();
+}
+
+static QString sectionName(const QString& section)
+{
+ if (section == "1")
+ return i18n("User Commands");
+ else if (section == "2")
+ return i18n("System Calls");
+ else if (section == "3")
+ return i18n("Subroutines");
+ else if (section == "3p")
+ return i18n("Perl Modules");
+ else if (section == "3n")
+ return i18n("Network Functions");
+ else if (section == "4")
+ return i18n("Devices");
+ else if (section == "5")
+ return i18n("File Formats");
+ else if (section == "6")
+ return i18n("Games");
+ else if (section == "7")
+ return i18n("Miscellaneous");
+ else if (section == "8")
+ return i18n("System Administration");
+ else if (section == "9")
+ return i18n("Kernel");
+ else if (section == "l")
+ return i18n("Local Documentation");
+ else if (section == "n")
+ return i18n("New");
+
+ return QString::null;
+}
+
+QStringList MANProtocol::buildSectionList(const QStringList& dirs) const
+{
+ QStringList l;
+
+ for (QStringList::ConstIterator it = section_names.begin();
+ it != section_names.end(); ++it)
+ {
+ for (QStringList::ConstIterator dir = dirs.begin();
+ dir != dirs.end(); ++dir)
+ {
+ QDir d((*dir)+"/man"+(*it));
+ if (d.exists())
+ {
+ l << *it;
+ break;
+ }
+ }
+ }
+ return l;
+}
+
+void MANProtocol::showMainIndex()
+{
+ QByteArray array;
+ QTextStream os(array, IO_WriteOnly);
+ os.setEncoding(QTextStream::UnicodeUTF8);
+
+ // print header
+ os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl;
+ os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl;
+ os << "<title>" << i18n("UNIX Manual Index") << "</title>" << endl;
+ if (!m_manCSSFile.isEmpty())
+ os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl;
+ os << "</head>" << endl;
+ os << "<body><h1>" << i18n("UNIX Manual Index") << "</h1>" << endl;
+
+ // ### TODO: why still the environment variable
+ const QString sectList = getenv("MANSECT");
+ QStringList sections;
+ if (sectList.isEmpty())
+ sections = buildSectionList(manDirectories());
+ else
+ sections = QStringList::split(':', sectList);
+
+ os << "<table>" << endl;
+
+ QStringList::ConstIterator it;
+ for (it = sections.begin(); it != sections.end(); ++it)
+ os << "<tr><td><a href=\"man:(" << *it << ")\" accesskey=\"" <<
+ (((*it).length()==1)?(*it):(*it).right(1))<<"\">" << i18n("Section ")
+ << *it << "</a></td><td>&nbsp;</td><td> " << sectionName(*it) << "</td></tr>" << endl;
+
+ os << "</table>" << endl;
+
+ // print footer
+ os << "</body></html>" << endl;
+
+ data(array);
+ finished();
+}
+
+void MANProtocol::constructPath(QStringList& constr_path, QStringList constr_catmanpath)
+{
+ QMap<QString, QString> manpath_map;
+ QMap<QString, QString> mandb_map;
+
+ // Add paths from /etc/man.conf
+ //
+ // Explicit manpaths may be given by lines starting with "MANPATH" or
+ // "MANDATORY_MANPATH" (depending on system ?).
+ // Mappings from $PATH to manpath are given by lines starting with
+ // "MANPATH_MAP"
+
+ QRegExp manpath_regex( "^MANPATH\\s" );
+ QRegExp mandatory_regex( "^MANDATORY_MANPATH\\s" );
+ QRegExp manpath_map_regex( "^MANPATH_MAP\\s" );
+ QRegExp mandb_map_regex( "^MANDB_MAP\\s" );
+ //QRegExp section_regex( "^SECTION\\s" );
+ QRegExp space_regex( "\\s+" ); // for parsing manpath map
+
+ QFile mc("/etc/man.conf"); // Caldera
+ if (!mc.exists())
+ mc.setName("/etc/manpath.config"); // SuSE, Debian
+ if (!mc.exists())
+ mc.setName("/etc/man.config"); // Mandrake
+
+ if (mc.open(IO_ReadOnly))
+ {
+ QTextStream is(&mc);
+ is.setEncoding(QTextStream::Locale);
+
+ while (!is.eof())
+ {
+ const QString line = is.readLine();
+ if ( manpath_regex.search(line, 0) == 0 )
+ {
+ const QString path = line.mid(8).stripWhiteSpace();
+ constr_path += path;
+ }
+ else if ( mandatory_regex.search(line, 0) == 0 )
+ {
+ const QString path = line.mid(18).stripWhiteSpace();
+ constr_path += path;
+ }
+ else if ( manpath_map_regex.search(line, 0) == 0 )
+ {
+ // The entry is "MANPATH_MAP <path> <manpath>"
+ const QStringList mapping =
+ QStringList::split(space_regex, line);
+
+ if ( mapping.count() == 3 )
+ {
+ const QString dir = QDir::cleanDirPath( mapping[1] );
+ const QString mandir = QDir::cleanDirPath( mapping[2] );
+
+ manpath_map[ dir ] = mandir;
+ }
+ }
+ else if ( mandb_map_regex.search(line, 0) == 0 )
+ {
+ // The entry is "MANDB_MAP <manpath> <catmanpath>"
+ const QStringList mapping =
+ QStringList::split(space_regex, line);
+
+ if ( mapping.count() == 3 )
+ {
+ const QString mandir = QDir::cleanDirPath( mapping[1] );
+ const QString catmandir = QDir::cleanDirPath( mapping[2] );
+
+ mandb_map[ mandir ] = catmandir;
+ }
+ }
+ /* sections are not used
+ else if ( section_regex.find(line, 0) == 0 )
+ {
+ if ( !conf_section.isEmpty() )
+ conf_section += ':';
+ conf_section += line.mid(8).stripWhiteSpace();
+ }
+ */
+ }
+ mc.close();
+ }
+
+ // Default paths
+ static const char *manpaths[] = {
+ "/usr/X11/man",
+ "/usr/X11R6/man",
+ "/usr/man",
+ "/usr/local/man",
+ "/usr/exp/man",
+ "/usr/openwin/man",
+ "/usr/dt/man",
+ "/opt/freetool/man",
+ "/opt/local/man",
+ "/usr/tex/man",
+ "/usr/www/man",
+ "/usr/lang/man",
+ "/usr/gnu/man",
+ "/usr/share/man",
+ "/usr/motif/man",
+ "/usr/titools/man",
+ "/usr/sunpc/man",
+ "/usr/ncd/man",
+ "/usr/newsprint/man",
+ NULL };
+
+
+ int i = 0;
+ while (manpaths[i]) {
+ if ( constr_path.findIndex( QString( manpaths[i] ) ) == -1 )
+ constr_path += QString( manpaths[i] );
+ i++;
+ }
+
+ // Directories in $PATH
+ // - if a manpath mapping exists, use that mapping
+ // - if a directory "<path>/man" or "<path>/../man" exists, add it
+ // to the man path (the actual existence check is done further down)
+
+ if ( ::getenv("PATH") ) {
+ const QStringList path =
+ QStringList::split( ":",
+ QString::fromLocal8Bit( ::getenv("PATH") ) );
+
+ for ( QStringList::const_iterator it = path.begin();
+ it != path.end();
+ ++it )
+ {
+ const QString dir = QDir::cleanDirPath( *it );
+ QString mandir = manpath_map[ dir ];
+
+ if ( !mandir.isEmpty() ) {
+ // a path mapping exists
+ if ( constr_path.findIndex( mandir ) == -1 )
+ constr_path += mandir;
+ }
+ else {
+ // no manpath mapping, use "<path>/man" and "<path>/../man"
+
+ mandir = dir + QString( "/man" );
+ if ( constr_path.findIndex( mandir ) == -1 )
+ constr_path += mandir;
+
+ int pos = dir.findRev( '/' );
+ if ( pos > 0 ) {
+ mandir = dir.left( pos ) + QString("/man");
+ if ( constr_path.findIndex( mandir ) == -1 )
+ constr_path += mandir;
+ }
+ }
+ QString catmandir = mandb_map[ mandir ];
+ if ( !mandir.isEmpty() )
+ {
+ if ( constr_catmanpath.findIndex( catmandir ) == -1 )
+ constr_catmanpath += catmandir;
+ }
+ else
+ {
+ // What is the default mapping?
+ catmandir = mandir;
+ catmandir.replace("/usr/share/","/var/cache/");
+ if ( constr_catmanpath.findIndex( catmandir ) == -1 )
+ constr_catmanpath += catmandir;
+ }
+ }
+ }
+}
+
+void MANProtocol::checkManPaths()
+{
+ static bool inited = false;
+
+ if (inited)
+ return;
+
+ inited = true;
+
+ const QString manpath_env = QString::fromLocal8Bit( ::getenv("MANPATH") );
+ //QString mansect_env = QString::fromLocal8Bit( ::getenv("MANSECT") );
+
+ // Decide if $MANPATH is enough on its own or if it should be merged
+ // with the constructed path.
+ // A $MANPATH starting or ending with ":", or containing "::",
+ // should be merged with the constructed path.
+
+ bool construct_path = false;
+
+ if ( manpath_env.isEmpty()
+ || manpath_env[0] == ':'
+ || manpath_env[manpath_env.length()-1] == ':'
+ || manpath_env.contains( "::" ) )
+ {
+ construct_path = true; // need to read config file
+ }
+
+ // Constucted man path -- consists of paths from
+ // /etc/man.conf
+ // default dirs
+ // $PATH
+ QStringList constr_path;
+ QStringList constr_catmanpath; // catmanpath
+
+ QString conf_section;
+
+ if ( construct_path )
+ {
+ constructPath(constr_path, constr_catmanpath);
+ }
+
+ m_mandbpath=constr_catmanpath;
+
+ // Merge $MANPATH with the constructed path to form the
+ // actual manpath.
+ //
+ // The merging syntax with ":" and "::" in $MANPATH will be
+ // satisfied if any empty string in path_list_env (there
+ // should be 1 or 0) is replaced by the constructed path.
+
+ const QStringList path_list_env = QStringList::split( ':', manpath_env , true );
+
+ for ( QStringList::const_iterator it = path_list_env.begin();
+ it != path_list_env.end();
+ ++it )
+ {
+ struct stat sbuf;
+
+ QString dir = (*it);
+
+ if ( !dir.isEmpty() ) {
+ // Add dir to the man path if it exists
+ if ( m_manpath.findIndex( dir ) == -1 ) {
+ if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0
+ && S_ISDIR( sbuf.st_mode ) )
+ {
+ m_manpath += dir;
+ }
+ }
+ }
+ else {
+ // Insert constructed path ($MANPATH was empty, or
+ // there was a ":" at an end or "::")
+
+ for ( QStringList::Iterator it2 = constr_path.begin();
+ it2 != constr_path.end();
+ it2++ )
+ {
+ dir = (*it2);
+
+ if ( !dir.isEmpty() ) {
+ if ( m_manpath.findIndex( dir ) == -1 ) {
+ if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0
+ && S_ISDIR( sbuf.st_mode ) )
+ {
+ m_manpath += dir;
+ }
+ }
+ }
+ }
+ }
+ }
+
+/* sections are not used
+ // Sections
+ QStringList m_mansect = QStringList::split( ':', mansect_env, true );
+
+ const char* default_sect[] =
+ { "1", "2", "3", "4", "5", "6", "7", "8", "9", "n", 0L };
+
+ for ( int i = 0; default_sect[i] != 0L; i++ )
+ if ( m_mansect.findIndex( QString( default_sect[i] ) ) == -1 )
+ m_mansect += QString( default_sect[i] );
+*/
+
+}
+
+
+//#define _USE_OLD_CODE
+
+#ifdef _USE_OLD_CODE
+#warning "using old code"
+#else
+
+// Define this, if you want to compile with qsort from stdlib.h
+// else the Qt Heapsort will be used.
+// Note, qsort seems to be a bit faster (~10%) on a large man section
+// eg. man section 3
+#define _USE_QSORT
+
+// Setup my own structure, with char pointers.
+// from now on only pointers are copied, no strings
+//
+// containing the whole path string,
+// the beginning of the man page name
+// and the length of the name
+struct man_index_t {
+ char *manpath; // the full path including man file
+ const char *manpage_begin; // pointer to the begin of the man file name in the path
+ int manpage_len; // len of the man file name
+};
+typedef man_index_t *man_index_ptr;
+
+#ifdef _USE_QSORT
+int compare_man_index(const void *s1, const void *s2)
+{
+ struct man_index_t *m1 = *(struct man_index_t **)s1;
+ struct man_index_t *m2 = *(struct man_index_t **)s2;
+ int i;
+ // Compare the names of the pages
+ // with the shorter length.
+ // Man page names are not '\0' terminated, so
+ // this is a bit tricky
+ if ( m1->manpage_len > m2->manpage_len)
+ {
+ i = qstrnicmp( m1->manpage_begin,
+ m2->manpage_begin,
+ m2->manpage_len);
+ if (!i)
+ return 1;
+ return i;
+ }
+
+ if ( m1->manpage_len < m2->manpage_len)
+ {
+ i = qstrnicmp( m1->manpage_begin,
+ m2->manpage_begin,
+ m1->manpage_len);
+ if (!i)
+ return -1;
+ return i;
+ }
+
+ return qstrnicmp( m1->manpage_begin,
+ m2->manpage_begin,
+ m1->manpage_len);
+}
+
+#else /* !_USE_QSORT */
+#warning using heapsort
+// Set up my own man page list,
+// with a special compare function to sort itself
+typedef QPtrList<struct man_index_t> QManIndexListBase;
+typedef QPtrListIterator<struct man_index_t> QManIndexListIterator;
+
+class QManIndexList : public QManIndexListBase
+{
+public:
+private:
+ int compareItems( QPtrCollection::Item s1, QPtrCollection::Item s2 )
+ {
+ struct man_index_t *m1 = (struct man_index_t *)s1;
+ struct man_index_t *m2 = (struct man_index_t *)s2;
+ int i;
+ // compare the names of the pages
+ // with the shorter length
+ if (m1->manpage_len > m2->manpage_len)
+ {
+ i = qstrnicmp(m1->manpage_begin,
+ m2->manpage_begin,
+ m2->manpage_len);
+ if (!i)
+ return 1;
+ return i;
+ }
+
+ if (m1->manpage_len > m2->manpage_len)
+ {
+
+ i = qstrnicmp(m1->manpage_begin,
+ m2->manpage_begin,
+ m1->manpage_len);
+ if (!i)
+ return -1;
+ return i;
+ }
+
+ return qstrnicmp(m1->manpage_begin,
+ m2->manpage_begin,
+ m1->manpage_len);
+ }
+};
+
+#endif /* !_USE_QSORT */
+#endif /* !_USE_OLD_CODE */
+
+
+
+
+void MANProtocol::showIndex(const QString& section)
+{
+ QByteArray array;
+ QTextStream os(array, IO_WriteOnly);
+ os.setEncoding(QTextStream::UnicodeUTF8);
+
+ // print header
+ os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl;
+ os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl;
+ os << "<title>" << i18n("UNIX Manual Index") << "</title>" << endl;
+ if ( !m_manCSSFile.isEmpty() )
+ os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl;
+ os << "</head>" << endl;
+ os << "<body><div class=\"secidxmain\">" << endl;
+ os << "<h1>" << i18n( "Index for Section %1: %2").arg(section).arg(sectionName(section)) << "</h1>" << endl;
+
+ // compose list of search paths -------------------------------------------------------------
+
+ checkManPaths();
+ infoMessage(i18n("Generating Index"));
+
+ // search for the man pages
+ QStringList pages = findPages( section, QString::null );
+
+ QMap<QString, QString> indexmap = buildIndexMap(section);
+
+ // print out the list
+ os << "<table>" << endl;
+
+#ifdef _USE_OLD_CODE
+ pages.sort();
+
+ QMap<QString, QString> pagemap;
+
+ QStringList::ConstIterator page;
+ for (page = pages.begin(); page != pages.end(); ++page)
+ {
+ QString fileName = *page;
+
+ stripExtension( &fileName );
+
+ pos = fileName.findRev('/');
+ if (pos > 0)
+ fileName = fileName.mid(pos+1);
+
+ if (!fileName.isEmpty())
+ pagemap[fileName] = *page;
+
+ }
+
+ for (QMap<QString,QString>::ConstIterator it = pagemap.begin();
+ it != pagemap.end(); ++it)
+ {
+ os << "<tr><td><a href=\"man:" << it.data() << "\">\n"
+ << it.key() << "</a></td><td>&nbsp;</td><td> "
+ << (indexmap.contains(it.key()) ? indexmap[it.key()] : "" )
+ << "</td></tr>" << endl;
+ }
+
+#else /* ! _USE_OLD_CODE */
+
+#ifdef _USE_QSORT
+
+ int listlen = pages.count();
+ man_index_ptr *indexlist = new man_index_ptr[listlen];
+ listlen = 0;
+
+#else /* !_USE_QSORT */
+
+ QManIndexList manpages;
+ manpages.setAutoDelete(TRUE);
+
+#endif /* _USE_QSORT */
+
+ QStringList::const_iterator page;
+ for (page = pages.begin(); page != pages.end(); ++page)
+ {
+ // I look for the beginning of the man page name
+ // i.e. "bla/pagename.3.gz" by looking for the last "/"
+ // Then look for the end of the name by searching backwards
+ // for the last ".", not counting zip extensions.
+ // If the len of the name is >0,
+ // store it in the list structure, to be sorted later
+
+ char *manpage_end;
+ struct man_index_t *manindex = new man_index_t;
+ manindex->manpath = strdup((*page).utf8());
+
+ manindex->manpage_begin = strrchr(manindex->manpath, '/');
+ if (manindex->manpage_begin)
+ {
+ manindex->manpage_begin++;
+ assert(manindex->manpage_begin >= manindex->manpath);
+ }
+ else
+ {
+ manindex->manpage_begin = manindex->manpath;
+ assert(manindex->manpage_begin >= manindex->manpath);
+ }
+
+ // Skip extension ".section[.gz]"
+
+ char *begin = (char*)(manindex->manpage_begin);
+ int len = strlen( begin );
+ char *end = begin+(len-1);
+
+ if ( len >= 3 && strcmp( end-2, ".gz" ) == 0 )
+ end -= 3;
+ else if ( len >= 2 && strcmp( end-1, ".Z" ) == 0 )
+ end -= 2;
+ else if ( len >= 2 && strcmp( end-1, ".z" ) == 0 )
+ end -= 2;
+ else if ( len >= 4 && strcmp( end-3, ".bz2" ) == 0 )
+ end -= 4;
+
+ while ( end >= begin && *end != '.' )
+ end--;
+
+ if ( end < begin )
+ manpage_end = 0;
+ else
+ manpage_end = end;
+
+ if (NULL == manpage_end)
+ {
+ // no '.' ending ???
+ // set the pointer past the end of the filename
+ manindex->manpage_len = (*page).length();
+ manindex->manpage_len -= (manindex->manpage_begin - manindex->manpath);
+ assert(manindex->manpage_len >= 0);
+ }
+ else
+ {
+ manindex->manpage_len = (manpage_end - manindex->manpage_begin);
+ assert(manindex->manpage_len >= 0);
+ }
+
+ if (0 < manindex->manpage_len)
+ {
+
+#ifdef _USE_QSORT
+
+ indexlist[listlen] = manindex;
+ listlen++;
+
+#else /* !_USE_QSORT */
+
+ manpages.append(manindex);
+
+#endif /* _USE_QSORT */
+
+ }
+ }
+
+ //
+ // Now do the sorting on the page names
+ // and the printout afterwards
+ // While printing avoid duplicate man page names
+ //
+
+ struct man_index_t dummy_index = {0l,0l,0};
+ struct man_index_t *last_index = &dummy_index;
+
+#ifdef _USE_QSORT
+
+ // sort and print
+ qsort(indexlist, listlen, sizeof(struct man_index_t *), compare_man_index);
+
+ QChar firstchar, tmp;
+ QString indexLine="<div class=\"secidxshort\">\n";
+ if (indexlist[0]->manpage_len>0)
+ {
+ firstchar=QChar((indexlist[0]->manpage_begin)[0]).lower();
+
+ const QString appendixstr = QString(
+ " [<a href=\"#%1\" accesskey=\"%2\">%3</a>]\n"
+ ).arg(firstchar).arg(firstchar).arg(firstchar);
+ indexLine.append(appendixstr);
+ }
+ os << "<tr><td class=\"secidxnextletter\"" << " colspan=\"3\">\n <a name=\""
+ << firstchar << "\">" << firstchar <<"</a>\n</td></tr>" << endl;
+
+ for (int i=0; i<listlen; i++)
+ {
+ struct man_index_t *manindex = indexlist[i];
+
+ // qstrncmp():
+ // "last_man" has already a \0 string ending, but
+ // "manindex->manpage_begin" has not,
+ // so do compare at most "manindex->manpage_len" of the strings.
+ if (last_index->manpage_len == manindex->manpage_len &&
+ !qstrncmp(last_index->manpage_begin,
+ manindex->manpage_begin,
+ manindex->manpage_len)
+ )
+ {
+ continue;
+ }
+
+ tmp=QChar((manindex->manpage_begin)[0]).lower();
+ if (firstchar != tmp)
+ {
+ firstchar = tmp;
+ os << "<tr><td class=\"secidxnextletter\"" << " colspan=\"3\">\n <a name=\""
+ << firstchar << "\">" << firstchar << "</a>\n</td></tr>" << endl;
+
+ const QString appendixstr = QString(
+ " [<a href=\"#%1\" accesskey=\"%2\">%3</a>]\n"
+ ).arg(firstchar).arg(firstchar).arg(firstchar);
+ indexLine.append(appendixstr);
+ }
+ os << "<tr><td><a href=\"man:"
+ << manindex->manpath << "\">\n";
+
+ ((char *)manindex->manpage_begin)[manindex->manpage_len] = '\0';
+ os << manindex->manpage_begin
+ << "</a></td><td>&nbsp;</td><td> "
+ << (indexmap.contains(manindex->manpage_begin) ? indexmap[manindex->manpage_begin] : "" )
+ << "</td></tr>" << endl;
+ last_index = manindex;
+ }
+ indexLine.append("</div>");
+
+ for (int i=0; i<listlen; i++) {
+ ::free(indexlist[i]->manpath); // allocated by strdup
+ delete indexlist[i];
+ }
+
+ delete [] indexlist;
+
+#else /* !_USE_QSORT */
+
+ manpages.sort(); // using
+
+ for (QManIndexListIterator mit(manpages);
+ mit.current();
+ ++mit )
+ {
+ struct man_index_t *manindex = mit.current();
+
+ // qstrncmp():
+ // "last_man" has already a \0 string ending, but
+ // "manindex->manpage_begin" has not,
+ // so do compare at most "manindex->manpage_len" of the strings.
+ if (last_index->manpage_len == manindex->manpage_len &&
+ !qstrncmp(last_index->manpage_begin,
+ manindex->manpage_begin,
+ manindex->manpage_len)
+ )
+ {
+ continue;
+ }
+
+ os << "<tr><td><a href=\"man:"
+ << manindex->manpath << "\">\n";
+
+ manindex->manpage_begin[manindex->manpage_len] = '\0';
+ os << manindex->manpage_begin
+ << "</a></td><td>&nbsp;</td><td> "
+ << (indexmap.contains(manindex->manpage_begin) ? indexmap[manindex->manpage_begin] : "" )
+ << "</td></tr>" << endl;
+ last_index = manindex;
+ }
+#endif /* _USE_QSORT */
+#endif /* _USE_OLD_CODE */
+
+ os << "</table></div>" << endl;
+
+ os << indexLine << endl;
+
+ // print footer
+ os << "</body></html>" << endl;
+
+ infoMessage(QString::null);
+ mimeType("text/html");
+ data(array);
+ finished();
+}
+
+void MANProtocol::listDir(const KURL &url)
+{
+ kdDebug( 7107 ) << "ENTER listDir: " << url.prettyURL() << endl;
+
+ QString title;
+ QString section;
+
+ if ( !parseUrl(url.path(), title, section) ) {
+ error( KIO::ERR_MALFORMED_URL, url.url() );
+ return;
+ }
+
+ QStringList list = findPages( section, QString::null, false );
+
+ UDSEntryList uds_entry_list;
+ UDSEntry uds_entry;
+ UDSAtom uds_atom;
+
+ uds_atom.m_uds = KIO::UDS_NAME; // we only do names...
+ uds_entry.append( uds_atom );
+
+ QStringList::Iterator it = list.begin();
+ QStringList::Iterator end = list.end();
+
+ for ( ; it != end; ++it ) {
+ stripExtension( &(*it) );
+
+ uds_entry[0].m_str = *it;
+ uds_entry_list.append( uds_entry );
+ }
+
+ listEntries( uds_entry_list );
+ finished();
+}
+
+void MANProtocol::getProgramPath()
+{
+ if (!mySgml2RoffPath.isEmpty())
+ return;
+
+ mySgml2RoffPath = KGlobal::dirs()->findExe("sgml2roff");
+ if (!mySgml2RoffPath.isEmpty())
+ return;
+
+ /* sgml2roff isn't found in PATH. Check some possible locations where it may be found. */
+ mySgml2RoffPath = KGlobal::dirs()->findExe("sgml2roff", QString(SGML2ROFF_DIRS));
+ if (!mySgml2RoffPath.isEmpty())
+ return;
+
+ /* Cannot find sgml2roff programm: */
+ outputError(i18n("Could not find the sgml2roff program on your system. Please install it, if necessary, and extend the search path by adjusting the environment variable PATH before starting KDE."));
+ finished();
+ exit();
+}
diff --git a/kioslave/man/kio_man.css b/kioslave/man/kio_man.css
new file mode 100644
index 000000000..8a9f378bc
--- /dev/null
+++ b/kioslave/man/kio_man.css
@@ -0,0 +1,21 @@
+body {background-color:#fffff}
+
+/*for the list of one manpage section*/
+.secidxshort {
+ display:block;
+ position:absolute;overflow:auto;
+ top:0px;bottom:95%;left:0;right:0;
+}
+.secidxmain {
+/*misfortunately accessing anchors in a scrollview
+ doesn't seem to work yet with konqi, so:*/
+/*
+ position:absolute;overflow:auto;
+ top:5%;bottom:0;left:0;right:0;
+*/
+}
+.secidxnextletter {
+ font-size:larger;
+ border-bottom:1px solid black;
+ text-align:center
+}
diff --git a/kioslave/man/kio_man.h b/kioslave/man/kio_man.h
new file mode 100644
index 000000000..f571082db
--- /dev/null
+++ b/kioslave/man/kio_man.h
@@ -0,0 +1,99 @@
+/* This file is part of the KDE libraries
+ Copyright (c) 2000 Matthias Hoelzer-Kluepfel <mhk@caldera.de>
+
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef __kio_man_h__
+#define __kio_man_h__
+
+
+#include <qstring.h>
+#include <qcstring.h>
+#include <qstringlist.h>
+#include <qdict.h>
+#include <qbuffer.h>
+
+
+#include <kio/global.h>
+#include <kio/slavebase.h>
+
+
+class MANProtocol : public QObject, public KIO::SlaveBase
+{
+ Q_OBJECT
+
+public:
+
+ MANProtocol(const QCString &pool_socket, const QCString &app_socket);
+ virtual ~MANProtocol();
+
+ virtual void get(const KURL& url);
+ virtual void stat(const KURL& url);
+
+ virtual void mimetype(const KURL &url);
+ virtual void listDir(const KURL &url);
+
+ void outputError(const QString& errmsg);
+ void outputMatchingPages(const QStringList &matchingPages);
+
+ void showMainIndex();
+ void showIndex(const QString& section);
+
+ // the following two functions are the interface to man2html
+ void output(const char *insert);
+ char *readManPage(const char *filename);
+
+ static MANProtocol *self();
+
+private slots:
+ void slotGetStdOutput(KProcess*, char*, int);
+
+private:
+ void checkManPaths();
+ QStringList manDirectories();
+ QMap<QString, QString> buildIndexMap(const QString& section);
+ bool addWhatIs(QMap<QString, QString>& i, const QString& f, const QString& mark);
+ void parseWhatIs( QMap<QString, QString> &i, QTextStream &t, const QString &mark );
+ QStringList findPages(const QString& section,
+ const QString &title,
+ bool full_path = true);
+
+ void addToBuffer(const char *buffer, int buflen);
+ QString pageName(const QString& page) const;
+ QStringList buildSectionList(const QStringList& dirs) const;
+ void constructPath(QStringList& constr_path, QStringList constr_catmanpath);
+private:
+ static MANProtocol *_self;
+ QCString lastdir;
+
+ void findManPagesInSection(const QString &dir, const QString &title, bool full_path, QStringList &list);
+ QStringList m_manpath; ///< Path of man directories
+ QStringList m_mandbpath; ///< Path of catman directories
+ QStringList section_names;
+
+ QString myStdStream;
+ QString mySgml2RoffPath;
+ void getProgramPath();
+
+ QCString m_htmlPath; ///< Path to KDE resources, encoded for HTML
+ QCString m_cssPath; ///< Path to KDE resources, encoded for CSS
+ QBuffer m_outputBuffer; ///< Buffer for the output
+ QString m_manCSSFile; ///< Path to kio_man.css
+};
+
+
+#endif
diff --git a/kioslave/man/kio_man_test.cpp b/kioslave/man/kio_man_test.cpp
new file mode 100644
index 000000000..bfb78a652
--- /dev/null
+++ b/kioslave/man/kio_man_test.cpp
@@ -0,0 +1,38 @@
+
+
+#include <qobject.h>
+
+#include "kio_man.h"
+
+
+#include <kapplication.h>
+#include <klocale.h>
+
+
+class kio_man_test : public MANProtocol
+{
+ Q_OBJECT
+
+public:
+ kio_man_test(const QCString &pool_socket, const QCString &app_socket);
+
+protected:
+ virtual void data(int);
+
+};
+
+
+
+
+
+int main(int argc, char **argv)
+{
+ KApplication a( argc, argv , "p2");
+
+ MANProtocol testproto("/tmp/kiotest.in", "/tmp/kiotest.out");
+ testproto.showIndex("3");
+
+ return 0;
+}
+
+
diff --git a/kioslave/man/kmanpart.cpp b/kioslave/man/kmanpart.cpp
new file mode 100644
index 000000000..307a8c6b2
--- /dev/null
+++ b/kioslave/man/kmanpart.cpp
@@ -0,0 +1,115 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Alexander Neundorf <neundorf@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kmanpart.h"
+#include <qstring.h>
+
+#include <kinstance.h>
+#include <kglobal.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kaboutdata.h>
+#include <kdeversion.h>
+
+extern "C"
+{
+ KDE_EXPORT void* init_libkmanpart()
+ {
+ return new KManPartFactory;
+ }
+}
+
+KInstance* KManPartFactory::s_instance = 0L;
+KAboutData* KManPartFactory::s_about = 0L;
+
+KManPartFactory::KManPartFactory( QObject* parent, const char* name )
+ : KParts::Factory( parent, name )
+{}
+
+KManPartFactory::~KManPartFactory()
+{
+ delete s_instance;
+ s_instance = 0L;
+ delete s_about;
+}
+
+KParts::Part* KManPartFactory::createPartObject( QWidget * parentWidget, const char* /*widgetName*/, QObject *,
+ const char* name, const char* /*className*/,const QStringList & )
+{
+ KManPart* part = new KManPart(parentWidget, name );
+ return part;
+}
+
+KInstance* KManPartFactory::instance()
+{
+ if( !s_instance )
+ {
+ s_about = new KAboutData( "kmanpart",
+ I18N_NOOP( "KMan" ), KDE_VERSION_STRING );
+ s_instance = new KInstance( s_about );
+ }
+ return s_instance;
+}
+
+
+KManPart::KManPart( QWidget * parent, const char * name )
+: KHTMLPart( parent, name )
+,m_job(0)
+{
+ KInstance * instance = new KInstance( "kmanpart" );
+ setInstance( instance );
+ m_extension=new KParts::BrowserExtension(this);
+}
+
+bool KManPart::openURL( const KURL &url )
+{
+ return KParts::ReadOnlyPart::openURL(url);
+}
+
+bool KManPart::openFile()
+{
+ if (m_job!=0)
+ m_job->kill();
+
+ begin();
+
+ KURL url;
+ url.setProtocol( "man" );
+ url.setPath( m_file );
+
+ m_job = KIO::get( url, true, false );
+ connect( m_job, SIGNAL( data( KIO::Job *, const QByteArray &) ), SLOT( readData( KIO::Job *, const QByteArray &) ) );
+ connect( m_job, SIGNAL( result( KIO::Job * ) ), SLOT( jobDone( KIO::Job * ) ) );
+ return true;
+}
+
+void KManPart::readData(KIO::Job * , const QByteArray & data)
+{
+ write(data,data.size());
+}
+
+void KManPart::jobDone( KIO::Job *)
+{
+ m_job=0;
+ end();
+}
+
+#include "kmanpart.moc"
+
diff --git a/kioslave/man/kmanpart.desktop b/kioslave/man/kmanpart.desktop
new file mode 100644
index 000000000..1d9bb06bf
--- /dev/null
+++ b/kioslave/man/kmanpart.desktop
@@ -0,0 +1,91 @@
+[Desktop Entry]
+Type=Service
+Comment=Embeddable Troff Viewer
+Comment[af]=Inlegbare Troff Kyker
+Comment[ar]=مستعرض Troff المدمج
+Comment[be]=Убудаваны праглядальнік Troff
+Comment[bg]=Визуализатор за вграждане на Troff
+Comment[bn]=সন্নিবেশযোগ্য ট্রফ্ প্রদর্শক
+Comment[bs]=Ugradivi preglednik Troff datoteka
+Comment[ca]=Visor Troff encastable
+Comment[cs]=Komponenta pro zobrazování manuálových stránek
+Comment[csb]=Przezérnik lopków troff
+Comment[cy]=Gwelydd Troff Mewnadeiladwy
+Comment[da]=Indlejrbar Troff-fremviser
+Comment[de]=Eingebetteter Troff-Betrachter
+Comment[el]=Ενσωματώσιμος προβολέας Troff
+Comment[eo]=Enkonstruebla bildrigardilo
+Comment[es]=Visor empotrable de Troff
+Comment[et]=Põimitav Troff komponent
+Comment[eu]=Troff ikustailu txertagarria
+Comment[fa]=مشاهده‌گر Troff نهفتنی
+Comment[fi]=Upotettava Troff-näytin
+Comment[fr]=Afficheur troff incorporé
+Comment[fy]=Ynsletten Troff-werjefteprogramma
+Comment[ga]=Amharcán Inleabaithe troff
+Comment[gl]=Visor de Troff Incrustábel
+Comment[he]=מציג Troff בר־הטבעה
+Comment[hi]=एम्बेडेबल ट्राफ प्रदर्शक
+Comment[hr]=Ugradivi preglednik slika
+Comment[hu]=Beágyazható Troff-komponens
+Comment[is]=Ívefjanlegur troff skoðari
+Comment[it]=Visualizzatore integrabile di file Troff
+Comment[ja]=埋め込み Troff ビューア
+Comment[ka]=ჩაშენებული დამთვალიერებელი პროგრამა Troff
+Comment[kk]=Құрамына енгізілетін Troff қарау құралы
+Comment[km]=កម្មវិធី​មើល Troff ដែល​អាច​បង្កប់​បាន
+Comment[ko]=포함 가능한 Troff 뷰어
+Comment[lo]=ຄອມໂປເນັນຕົວສະແດງພາບທີ່ຝັ່ງໄດ້
+Comment[lt]=Įdedamas Troff žiūriklis
+Comment[lv]=Iegults Troff skatītājs
+Comment[mk]=Вгнездлив Troff прегледувач
+Comment[mn]=Холбох боломжит Troff-Харагч
+Comment[ms]=Pemapar Troff Boleh Benam
+Comment[mt]=Werrej integrat Troff
+Comment[nb]=Innebyggbar Troff-viser
+Comment[nds]=Kieker för Troff, de inbett warrn kann
+Comment[ne]=सम्मिलित गर्न सकिने ट्रफ दर्शक
+Comment[nl]=Ingebed Troff-weergaveprogramma
+Comment[nn]=Innebyggbar Troff-visar
+Comment[nso]=Selebeledi seo se Robaditswego sa Troff
+Comment[pa]=ਸ਼ਾਮਿਲ ਹੋਣਯੋਗ Troff ਦਰਸ਼ਕ
+Comment[pl]=Przeglądania plików troff
+Comment[pt]=Visualizador de Troff incorporado
+Comment[pt_BR]=Visualizar Troff integrado
+Comment[ro]=Componentă de vizualizare Troff înglobată
+Comment[ru]=Встраиваемая программа просмотра Troff
+Comment[rw]=Mugaragaza Troff Ishyirwamo
+Comment[se]=Vuojohahtti Troff-cájeheaddji
+Comment[sk]=Vložiteľný prehliadač Troff
+Comment[sl]=Vključen pregledovalnik za Troff
+Comment[sr]=Уградиви Troff приказивач
+Comment[sr@Latn]=Ugradivi Troff prikazivač
+Comment[sv]=Inbäddningsbar Troff-visare
+Comment[ta]=உட்பொதிந்த ட்ராஃப் காட்சி
+Comment[te]=పొదగదగ్గ ట్రాఫ్ వీక్షిణి
+Comment[tg]=Биношавандаи барномаи намоишгари Troff
+Comment[th]=โปรแกรมดู Troff ที่สามารถฝังตัวได้
+Comment[tr]=Gömülebilir Troff Görüntüleyici
+Comment[tt]=Quşılulı Troff-Kürsätkeç
+Comment[uk]=Вмонтований переглядач Troff
+Comment[uz]=Ichiga oʻrnatib boʻladigan Troff-faylini koʻruvchi
+Comment[uz@cyrillic]=Ичига ўрнатиб бўладиган Troff-файлини кўрувчи
+Comment[ven]=Tshivhoni tsha Troff tsho dzheniswaho
+Comment[vi]=Trình xem Troff nhúng được
+Comment[wa]=Ravalåve håyneu di fitchîs Troff
+Comment[xh]=Umboniseli we Troff Elungiselekayo
+Comment[zh_CN]=嵌入的 Troff 查看器
+Comment[zh_TW]=可嵌入的 Troff 檢視元件
+Comment[zu]=Umbukisi we-Troff Oshuthekiwe
+MimeType=application/x-troff;application/x-troff-man;
+Name=KManPart
+Name[eo]=KMan-parto
+Name[hi]=के-मेन-पार्ट
+Name[lo]=ຕົວຮງກພື້ນທີ່ທຳງານ - K
+Name[ne]=K म्यान भाग
+Name[pt_BR]=Componente KMan
+Name[ro]=Componentă KMan
+Name[sv]=Kman-del
+Name[te]=కెమేన్ భాగం
+ServiceTypes=KParts/ReadOnlyPart,Browser/View
+X-KDE-Library=libkmanpart
diff --git a/kioslave/man/kmanpart.h b/kioslave/man/kmanpart.h
new file mode 100644
index 000000000..f68b68784
--- /dev/null
+++ b/kioslave/man/kmanpart.h
@@ -0,0 +1,79 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Alexander Neundorf <neundorf@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+
+#ifndef KMANPART_H
+#define KMANPART_H
+
+#include <kparts/factory.h>
+#include <kparts/part.h>
+#include <kparts/browserextension.h>
+#include <khtml_part.h>
+#include <kio/job.h>
+#include <kio/jobclasses.h>
+
+#include <qcstring.h>
+
+class KInstance;
+class KAboutData;
+
+/**
+ * Man Page Viewer
+ * \todo: Why is it needed? Why is KHTML alone not possible?
+ */
+class KManPartFactory: public KParts::Factory
+{
+ Q_OBJECT
+ public:
+ KManPartFactory( QObject * parent = 0, const char * name = 0 );
+ virtual ~KManPartFactory();
+
+ virtual KParts::Part* createPartObject( QWidget * parentWidget, const char * widgetName ,
+ QObject* parent, const char* name, const char * classname,
+ const QStringList &args);
+
+ static KInstance * instance();
+
+ private:
+ static KInstance * s_instance;
+ static KAboutData * s_about;
+
+};
+
+class KManPart : public KHTMLPart
+{
+ Q_OBJECT
+ public:
+ KManPart( QWidget * parent, const char * name = 0L );
+ KParts::BrowserExtension * extension() {return m_extension;}
+
+ public slots:
+ virtual bool openURL( const KURL &url );
+ protected slots:
+ void readData(KIO::Job * , const QByteArray & data);
+ void jobDone( KIO::Job *);
+ protected:
+ virtual bool openFile();
+ KInstance *m_instance;
+ KParts::BrowserExtension * m_extension;
+ KIO::TransferJob *m_job;
+};
+
+#endif
+
diff --git a/kioslave/man/man.protocol b/kioslave/man/man.protocol
new file mode 100644
index 000000000..5df21f19d
--- /dev/null
+++ b/kioslave/man/man.protocol
@@ -0,0 +1,12 @@
+[Protocol]
+exec=kio_man
+protocol=man
+input=none
+output=filesystem
+reading=true
+listing=Name
+defaultMimetype=text/html
+determineMimetypeFromExtension=false
+DocPath=kioslave/man.html
+Icon=man
+Class=:local
diff --git a/kioslave/man/man2html.cpp b/kioslave/man/man2html.cpp
new file mode 100644
index 000000000..725fba36a
--- /dev/null
+++ b/kioslave/man/man2html.cpp
@@ -0,0 +1,5683 @@
+/*
+ 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 <qvaluestack.h>
+#include <qstring.h>
+#include <qptrlist.h>
+#include <qmap.h>
+#include <qdatetime.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 <qtextcodec.h>
+# include <kdebug.h>
+# include <kdeversion.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;
+ qstrncpy(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
+ QCString 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 QMap<QCString,StringDefinition> s_characterDefinitionMap;
+
+/**
+ * Map of string variable and macro definitions
+ * \note String variables and macros are the same thing!
+ */
+static QMap<QCString,StringDefinition> s_stringDefinitionMap;
+
+/**
+ * Map of number registers
+ * \note Intern number registers (starting with a dot are not handled here)
+ */
+static QMap<QCString,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( "&lt;-", StringDefinition( 1, "&larr;" ) ); // <-
+ s_characterDefinitionMap.insert( "-&gt;", StringDefinition( 1, "&rarr;" ) ); // ->
+ s_characterDefinitionMap.insert( "&lt;&gt;", StringDefinition( 1, "&harr;" ) ); // <>
+ s_characterDefinitionMap.insert( "&lt;=", StringDefinition( 1, "&le;" ) ); // <=
+ s_characterDefinitionMap.insert( "&gt;=", StringDefinition( 1, "&ge;" ) ); // >=
+ // End HACK
+}
+
+/**
+ * Initialize string variables
+ */
+static void InitStringDefinitions( void )
+{
+ // mdoc-only, see mdoc.samples(7)
+ s_stringDefinitionMap.insert( "<=", StringDefinition( 1, "&le;" ) );
+ s_stringDefinitionMap.insert( ">=", StringDefinition( 1, "&ge;" ) );
+ s_stringDefinitionMap.insert( "Rq", StringDefinition( 1, "&rdquo;" ) );
+ s_stringDefinitionMap.insert( "Lq", StringDefinition( 1, "&ldquo;" ) );
+ s_stringDefinitionMap.insert( "ua", StringDefinition( 1, "&circ" ) ); // Note this is different from \(ua
+ s_stringDefinitionMap.insert( "aa", StringDefinition( 1, "&acute;" ) );
+ s_stringDefinitionMap.insert( "ga", StringDefinition( 1, "`" ) );
+ s_stringDefinitionMap.insert( "q", StringDefinition( 1, "&quot;" ) );
+ s_stringDefinitionMap.insert( "Pi", StringDefinition( 1, "&pi;" ) );
+ s_stringDefinitionMap.insert( "Ne", StringDefinition( 1, "&ne;" ) );
+ s_stringDefinitionMap.insert( "Le", StringDefinition( 1, "&le;" ) );
+ s_stringDefinitionMap.insert( "Ge", StringDefinition( 1, "&ge;" ) );
+ s_stringDefinitionMap.insert( "Lt", StringDefinition( 1, "&lt;" ) );
+ s_stringDefinitionMap.insert( "Gt", StringDefinition( 1, "&gt;" ) );
+ s_stringDefinitionMap.insert( "Pm", StringDefinition( 1, "&plusmn;" ) );
+ s_stringDefinitionMap.insert( "If", StringDefinition( 1, "&infin;" ) );
+ s_stringDefinitionMap.insert( "Na", StringDefinition( 3, "NaN" ) );
+ s_stringDefinitionMap.insert( "Ba", StringDefinition( 1, "|" ) );
+ // end mdoc-only
+ // man(7)
+ s_stringDefinitionMap.insert( "Tm", StringDefinition( 1, "&trade;" ) ); // \*(TM
+ s_stringDefinitionMap.insert( "R", StringDefinition( 1, "&reg;" ) ); // \*R
+ // end man(7)
+ // Missing characters from man(7):
+ // \*S "Change to default font size"
+#ifndef SIMPLE_MAN2HTML
+ // Special KDE KIO man:
+ const QCString kdeversion(KDE_VERSION_STRING);
+ s_stringDefinitionMap.insert( ".KDE_VERSION_STRING", StringDefinition( kdeversion.length(), kdeversion ) );
+#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
+ QDate today( QDate::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, "&Alpha;" },
+ { V('*','B'), 1, "&Beta;" },
+ { V('*','C'), 1, "&Xi;" },
+ { V('*','D'), 1, "&Delta;" },
+ { V('*','E'), 1, "&Epsilon;" },
+ { V('*','F'), 1, "&Phi;" },
+ { V('*','G'), 1, "&Gamma;" },
+ { V('*','H'), 1, "&Theta;" },
+ { V('*','I'), 1, "&Iota;" },
+ { V('*','K'), 1, "&Kappa;" },
+ { V('*','L'), 1, "&Lambda;" },
+ { V('*','M'), 1, "&Mu:" },
+ { V('*','N'), 1, "&Nu;" },
+ { V('*','O'), 1, "&Omicron;" },
+ { V('*','P'), 1, "&Pi;" },
+ { V('*','Q'), 1, "&Psi;" },
+ { V('*','R'), 1, "&Rho;" },
+ { V('*','S'), 1, "&Sigma;" },
+ { V('*','T'), 1, "&Tau;" },
+ { V('*','U'), 1, "&Upsilon;" },
+ { V('*','W'), 1, "&Omega;" },
+ { V('*','X'), 1, "&Chi;" },
+ { V('*','Y'), 1, "&Eta;" },
+ { V('*','Z'), 1, "&Zeta;" },
+ { V('*','a'), 1, "&alpha;"},
+ { V('*','b'), 1, "&beta;"},
+ { V('*','c'), 1, "&xi;"},
+ { V('*','d'), 1, "&delta;"},
+ { V('*','e'), 1, "&epsilon;"},
+ { V('*','f'), 1, "&phi;"},
+ { V('*','g'), 1, "&gamma;"},
+ { V('*','h'), 1, "&theta;"},
+ { V('*','i'), 1, "&iota;"},
+ { V('*','k'), 1, "&kappa;"},
+ { V('*','l'), 1, "&lambda;"},
+ { V('*','m'), 1, "&mu;" },
+ { V('*','n'), 1, "&nu;"},
+ { V('*','o'), 1, "&omicron;"},
+ { V('*','p'), 1, "&pi;"},
+ { V('*','q'), 1, "&psi;"},
+ { V('*','r'), 1, "&rho;"},
+ { V('*','s'), 1, "&sigma;"},
+ { V('*','t'), 1, "&tau;"},
+ { V('*','u'), 1, "&upsilon;"},
+ { V('*','w'), 1, "&omega;"},
+ { V('*','x'), 1, "&chi;"},
+ { V('*','y'), 1, "&eta;"},
+ { V('*','z'), 1, "&zeta;"},
+ { V('+','-'), 1, "&plusmn;" }, // not in groff_char(7)
+ { V('+','f'), 1, "&phi;"}, // phi1, we use the standard phi
+ { V('+','h'), 1, "&theta;"}, // theta1, we use the standard theta
+ { V('+','p'), 1, "&omega;"}, // omega1, we use the standard omega
+ { V('1','2'), 1, "&frac12;" },
+ { V('1','4'), 1, "&frac14;" },
+ { V('3','4'), 1, "&frac34;" },
+ { V('F','i'), 1, "&#xFB03;" }, // ffi ligature
+ { V('F','l'), 1, "&#xFB04;" }, // ffl ligature
+ { V('a','p'), 1, "~" },
+ { V('b','r'), 1, "|" },
+ { V('b','u'), 1, "&bull;" },
+ { V('b','v'), 1, "|" },
+ { V('c','i'), 1, "&#x25CB;" }, // circle ### TODO verify
+ { V('c','o'), 1, "&copy;" },
+ { V('c','t'), 1, "&cent;" },
+ { V('d','e'), 1, "&deg;" },
+ { V('d','g'), 1, "&dagger;" },
+ { V('d','i'), 1, "&divide;" },
+ { V('e','m'), 1, "&emdash;" },
+ { V('e','n'), 1, "&endash;"},
+ { V('e','q'), 1, "=" },
+ { V('e','s'), 1, "&empty;" },
+ { V('f','f'), 1, "&#0xFB00;" }, // ff ligature
+ { V('f','i'), 1, "&#0xFB01;" }, // fi ligature
+ { V('f','l'), 1, "&#0xFB02;" }, // fl ligature
+ { V('f','m'), 1, "&prime;" },
+ { V('g','a'), 1, "`" },
+ { V('h','y'), 1, "-" },
+ { V('l','c'), 2, "|&#175;" }, // ### 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, "&times;" },
+ { V('n','o'), 1, "&not;" },
+ { V('o','r'), 1, "|" },
+ { V('p','l'), 1, "+" },
+ { V('r','c'), 2, "&#175;|" }, // ### TODO: not in groff_char(7)
+ { V('r','f'), 2, "_|" }, // ### TODO: not in groff_char(7)
+ { V('r','g'), 1, "&reg;" },
+ { V('r','k'), 1, "<FONT SIZE=+2>}</FONT>" }, // ### TODO: not in groff_char(7)
+ { V('r','n'), 1, "&oline;" },
+ { V('r','u'), 1, "_" },
+ { V('s','c'), 1, "&sect;" },
+ { V('s','l'), 1, "/" },
+ { V('s','q'), 2, "&#x25A1" }, // WHITE SQUARE
+ { V('t','s'), 1, "&#x03C2;" }, // FINAL SIGMA
+ { V('u','l'), 1, "_" },
+ { V('-','D'), 1, "&ETH;" },
+ { V('S','d'), 1, "&eth;" },
+ { V('T','P'), 1, "&THORN;" },
+ { V('T','p'), 1, "&thorn;" },
+ { V('A','E'), 1, "&AElig;" },
+ { V('a','e'), 1, "&aelig;" },
+ { V('O','E'), 1, "&OElig;" },
+ { V('o','e'), 1, "&oelig;" },
+ { V('s','s'), 1, "&szlig;" },
+ { V('\'','A'), 1, "&Aacute;" },
+ { V('\'','E'), 1, "&Eacute;" },
+ { V('\'','I'), 1, "&Iacute;" },
+ { V('\'','O'), 1, "&Oacute;" },
+ { V('\'','U'), 1, "&Uacute;" },
+ { V('\'','Y'), 1, "&Yacute;" },
+ { V('\'','a'), 1, "&aacute;" },
+ { V('\'','e'), 1, "&eacute;" },
+ { V('\'','i'), 1, "&iacute;" },
+ { V('\'','o'), 1, "&oacute;" },
+ { V('\'','u'), 1, "&uacute;" },
+ { V('\'','y'), 1, "&yacute;" },
+ { V(':','A'), 1, "&Auml;" },
+ { V(':','E'), 1, "&Euml;" },
+ { V(':','I'), 1, "&Iuml;" },
+ { V(':','O'), 1, "&Ouml;" },
+ { V(':','U'), 1, "&Uuml;" },
+ { V(':','a'), 1, "&auml;" },
+ { V(':','e'), 1, "&euml;" },
+ { V(':','i'), 1, "&iuml;" },
+ { V(':','o'), 1, "&ouml;" },
+ { V(':','u'), 1, "&uuml;" },
+ { V(':','y'), 1, "&yuml;" },
+ { V('^','A'), 1, "&Acirc;" },
+ { V('^','E'), 1, "&Ecirc;" },
+ { V('^','I'), 1, "&Icirc;" },
+ { V('^','O'), 1, "&Ocirc;" },
+ { V('^','U'), 1, "&Ucirc;" },
+ { V('^','a'), 1, "&acirc;" },
+ { V('^','e'), 1, "&ecirc;" },
+ { V('^','i'), 1, "&icirc;" },
+ { V('^','o'), 1, "&ocirc;" },
+ { V('^','u'), 1, "&ucirc;" },
+ { V('`','A'), 1, "&Agrave;" },
+ { V('`','E'), 1, "&Egrave;" },
+ { V('`','I'), 1, "&Igrave;" },
+ { V('`','O'), 1, "&Ograve;" },
+ { V('`','U'), 1, "&Ugrave;" },
+ { V('`','a'), 1, "&agrave;" },
+ { V('`','e'), 1, "&egrave;" },
+ { V('`','i'), 1, "&igrave;" },
+ { V('`','o'), 1, "&ograve;" },
+ { V('`','u'), 1, "&ugrave;" },
+ { V('~','A'), 1, "&Atilde;" },
+ { V('~','N'), 1, "&Ntilde;" },
+ { V('~','O'), 1, "&Otilde;" },
+ { V('~','a'), 1, "&atilde" },
+ { V('~','n'), 1, "&ntidle;" },
+ { V('~','o'), 1, "&otidle;" },
+ { V(',','C'), 1, "&Ccedil;" },
+ { V(',','c'), 1, "&ccedil;" },
+ { V('/','L'), 1, "&#x0141;" },
+ { V('/','l'), 1, "&#x0142;" },
+ { V('/','O'), 1, "&Oslash;" },
+ { V('/','o'), 1, "&oslash;" },
+ { V('o','A'), 1, "&Aring;" },
+ { V('o','a'), 1, "&aring;" },
+ { V('a','"'), 1, "\"" },
+ { V('a','-'), 1, "&macr;" },
+ { V('a','.'), 1, "." },
+ { V('a','^'), 1, "&circ;" },
+ { V('a','a'), 1, "&acute;" },
+ { V('a','b'), 1, "`" },
+ { V('a','c'), 1, "&cedil;" },
+ { V('a','d'), 1, "&uml;" },
+ { V('a','h'), 1, "&#x02C2;" }, // caron
+ { V('a','o'), 1, "&#x02DA;" }, // ring
+ { V('a','~'), 1, "&tilde;" },
+ { V('h','o'), 1, "&#x02DB;" }, // ogonek
+ { V('.','i'), 1, "&#x0131;" }, // dot less i
+ { V('C','s'), 1, "&curren;" },
+ { V('D','o'), 1, "$" },
+ { V('P','o'), 1, "&pound;" },
+ { V('Y','e'), 1, "&yen;" },
+ { V('F','n'), 1, "&fnof;" },
+ { V('F','o'), 1, "&laquo;" },
+ { V('F','c'), 1, "&raquo;" },
+ { V('f','o'), 1, "&#x2039;" }, // single left guillemet
+ { V('f','c'), 1, "&#x203A;" }, // single right guillemet
+ { V('r','!'), 1, "&iecl;" },
+ { V('r','?'), 1, "&iquest;" },
+ { V('O','f'), 1, "&ordf" },
+ { V('O','m'), 1, "&ordm;" },
+ { V('p','c'), 1, "&middot;" },
+ { V('S','1'), 1, "&sup1;" },
+ { V('S','2'), 1, "&sup2;" },
+ { V('S','3'), 1, "&sup3;" },
+ { V('<','-'), 1, "&larr;" },
+ { V('-','>'), 1, "&rarr;" },
+ { V('<','>'), 1, "&harr;" },
+ { V('d','a'), 1, "&darr;" },
+ { V('u','a'), 1, "&uarr;" },
+ { V('l','A'), 1, "&lArr;" },
+ { V('r','A'), 1, "&rArr;" },
+ { V('h','A'), 1, "&hArr;" },
+ { V('d','A'), 1, "&dArr;" },
+ { V('u','A'), 1, "&uArr;" },
+ { V('b','a'), 1, "|" },
+ { V('b','b'), 1, "&brvbar;" },
+ { V('t','m'), 1, "&trade;" },
+ { V('d','d'), 1, "&Dagger;" },
+ { V('p','s'), 1, "&para;" },
+ { V('%','0'), 1, "&permil;" },
+ { V('f','/'), 1, "&frasl;" }, // Fraction slash
+ { V('s','d'), 1, "&Prime;" },
+ { 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, "&lt;" },
+ { V('r','a'), 1, "&gt;" },
+ { V('l','h'), 1, "&le;" },
+ { V('r','h'), 1, "&ge;" },
+ { V('B','q'), 1, "&bdquo;" },
+ { V('b','q'), 1, "&sbquo;" },
+ { V('l','q'), 1, "&ldquo;" },
+ { V('r','q'), 1, "&rdquo;" },
+ { V('o','q'), 1, "&lsquo;" },
+ { V('c','q'), 1, "&rsquo;" },
+ { V('a','q'), 1, "'" },
+ { V('d','q'), 1, "\"" },
+ { V('a','t'), 1, "@" },
+ { V('s','h'), 1, "#" },
+ { V('r','s'), 1, "\\" },
+ { V('t','f'), 1, "&there4;" },
+ { V('~','~'), 1, "&cong;" },
+ { V('~','='), 1, "&asymp;" },
+ { V('!','='), 1, "&ne;" },
+ { V('<','='), 1, "&le;" },
+ { V('=','='), 1, "&equiv;" },
+ { V('=','~'), 1, "&cong;" }, // ### TODO: verify
+ { V('>','='), 1, "&ge;" },
+ { V('A','N'), 1, "&and;" },
+ { V('O','R'), 1, "&or;" },
+ { V('t','e'), 1, "&exist;" },
+ { V('f','a'), 1, "&forall;" },
+ { V('A','h'), 1, "&alefsym;" },
+ { V('I','m'), 1, "&image;" },
+ { V('R','e'), 1, "&real;" },
+ { V('i','f'), 1, "&infin;" },
+ { V('m','d'), 1, "&sdot;" },
+ { V('m','o'), 1, "&#x2206;" }, // element ### TODO verify
+ { V('n','m'), 1, "&notin;" },
+ { V('p','t'), 1, "&prop;" },
+ { V('p','p'), 1, "&perp;" },
+ { V('s','b'), 1, "&sub;" },
+ { V('s','p'), 1, "&sup;" },
+ { V('i','b'), 1, "&sube;" },
+ { V('i','p'), 1, "&supe;" },
+ { V('i','s'), 1, "&int;" },
+ { V('s','r'), 1, "&radic;" },
+ { V('p','d'), 1, "&part;" },
+ { V('c','*'), 1, "&otimes;" },
+ { V('c','+'), 1, "&oplus;" },
+ { V('c','a'), 1, "&cap;" },
+ { V('c','u'), 1, "&cup;" },
+ { V('g','r'), 1, "V" }, // gradient ### TODO Where in Unicode?
+ { V('C','R'), 1, "&crarr;" },
+ { V('s','t'), 2, "-)" }, // "such that" ### TODO Where in Unicode?
+ { V('/','_'), 1, "&ang;" },
+ { V('w','p'), 1, "&weierp;" },
+ { V('l','z'), 1, "&loz;" },
+ { 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 QValueList<char*> s_argumentList;
+
+static QCString htmlPath, cssPath;
+
+static QCString s_dollarZero; // Value of $0
+
+void setResourcePath(const QCString& _htmlPath, const QCString& _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 };
+ QCString 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, *g,*h;
+ 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&gt;");
+ 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) {
+
+ QCString dir;
+ QCString file(g, h - g + 1);
+ file = file.stripWhiteSpace();
+ for (int index = 0; includedirs[index]; index++) {
+ QCString str = QCString(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;
+
+ QCString str;
+ str.sprintf("<A HREF=\"file:%s/%s\">%s</A>&gt;", 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 &nbsp;
+ 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 &nbsp;
+ kdDebug(7107) << "BEFORE SECTION:" << *h << endl;
+ if ( ( h > c + 5 ) && ( ! memcmp( h-5, "&nbsp;", 6 ) ) )
+ {
+ h -= 6;
+ kdDebug(7107) << "Skip &nbsp;" << 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;
+ QString subsec; // ### TODO avoid using QString, as we do not know the encoding
+ QString fstr(f); // ### TODO avoid using QString, 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';
+ QCString 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';
+ QCString 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 (!qstrncmp("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';
+ QCString 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';
+ QCString 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&gt;");
+ for (i=0; i<numtests; i++) nr += (idtest[i]!=NULL);
+ }
+ output_real(c);
+}
+
+static QCString 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 = qstrdup(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 QCString set_font( const QCString& name )
+{
+ // Every font but R (Regular) creates <span> elements
+ QCString 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 QCString set_font( const char ch )
+#ifndef SIMPLE_MAN2HTML
+ KDE_DEPRECATED;
+
+static QCString set_font( const char ch )
+#endif
+{
+ const QCString name = &ch;
+ return set_font( name );
+}
+
+static QCString 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 QCString font ( current_font );
+ QCString 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, QCString& cstr );
+
+/**
+ * scan a named character
+ * param c position
+*/
+static QCString scan_named_character( char*& c )
+{
+ QCString name;
+ if ( *c == '(' )
+ {
+ // \*(ab Name of two characters
+ if ( c[1] == escapesym )
+ {
+ QCString 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 )
+ {
+ QCString 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 )
+ {
+ QCString 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
+ QMap<QCString,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 QCString scan_named_string(char*& c)
+{
+ QCString name;
+ if ( *c == '(' )
+ {
+ // \*(ab Name of two characters
+ if ( c[1] == escapesym )
+ {
+ QCString 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 )
+ {
+ QCString 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
+ QMap<QCString,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 QCString 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 == '@' );
+ QValueList<char*>::const_iterator it = s_argumentList.begin();
+ QCString 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 QCString& 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 == ".KDE_VERSION_MAJOR" )
+ return KDE_VERSION_MAJOR;
+ else if ( name == ".KDE_VERSION_MINOR" )
+ return KDE_VERSION_MINOR;
+ else if ( name == ".KDE_VERSION_RELEASE" )
+ return KDE_VERSION_RELEASE;
+ else if ( name == ".KDE_VERSION" )
+ return KDE_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;
+ }
+ QCString 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
+ {
+ QMap< QCString, 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 QCString scan_named_font( char*& c )
+{
+ QCString name;
+ if ( *c == '(' )
+ {
+ // \f(ab Name of two characters
+ if ( c[1] == escapesym )
+ {
+ QCString 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 )
+ {
+ QCString 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 QCString scan_number_code( char*& c )
+{
+ QCString 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, QCString& 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 = "&nbsp;"; 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 = QCString( 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 = "&lt;";curpos++; break;
+ case '>': cstr = "&gt;";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;
+ QCString 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 = "&quot;"; curpos++; break;
+ default: cstr = char( i ); curpos++; break;
+ }
+ break;
+ }
+#endif
+ case '\'': cstr = "&acute;";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)
+{
+ QCString 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 = qstrdup(_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:
+ QPtrList<TABLEITEM> items;
+};
+
+TABLEITEM::TABLEITEM(TABLEROW *row) : contents(0), _parent(row) {
+ init();
+ _parent->addItem(this);
+}
+
+TABLEROW *TABLEROW::copyLayout() const {
+ TABLEROW *newrow = new TABLEROW();
+
+ QPtrListIterator<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;
+ QCString 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] && qstrncmp(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("&nbsp;");
+ 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 || qstrncmp(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 || qstrncmp(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 &amp; 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 QCString as if it would be a macro
+ const QCString 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 = qstrdup( 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] && qstrncmp(req, requests[r], len)) r++;
+ return requests[r] ? r : REQ_UNKNOWN;
+}
+
+// &%(#@ c programs !!!
+//static int ifelseval=0;
+// If/else can be nested!
+static QValueStack<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 QCString 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 QCString 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;
+ QCString 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 */
+ QMap<QCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(macroName);
+ if (it!=s_stringDefinitionMap.end())
+ {
+ kdDebug(7107) << "CALLING MACRO: " << macroName << endl;
+ const QCString 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] = qstrdup(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
+ qstrncpy(work+1,(*it).m_output.data(),length+1);
+ const QValueList<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 QCString name ( scan_identifier( c ) );
+ while (*c && *c!='\n') c++;
+ c++;
+ h=c;
+ while (*c && qstrncmp(c,".di",3)) while (*c && *c++!='\n');
+ *c='\0';
+ char* result=0;
+ scan_troff(h,0,&result);
+ QMap<QCString,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 QCString 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);
+ QMap<QCString,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 && qstrncmp(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 QCString 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 && qstrncmp(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("&nbsp;");
+ 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 = qstrncmp(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][qstrlen(wordlist[i])-1] == '\007')
+ wordlist[i][qstrlen(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(QTextCodec::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+=qstrlen(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 QCString name( scan_identifier( c ) );
+ if ( name.isEmpty() )
+ {
+ kdDebug(7107) << "EXCEPTION: empty origin string to remove/rename: " << endl;
+ break;
+ }
+ QCString 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);
+ QMap<QCString,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 QCString 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 );
+ QMap <QCString, 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 QCString name(nameStart);
+
+ QCString 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=qstrlen(endmacro);
+ while (*c && qstrncmp(c,endmacro,length))
+ c=skip_till_newline(c);
+
+ QCString macro;
+ while (sl!=c)
+ {
+ if (sl[0]=='\\' && sl[1]=='\\')
+ {
+ macro+='\\';
+ sl++;
+ }
+ else
+ macro+=*sl;
+ sl++;
+ }
+
+ QMap<QCString,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')
+ (*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 (qstrncmp(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&amp;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,"&lt;","&gt;");
+ 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,"&ldquo;","&rdquo;");
+ 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,"&quot;","&quot;");
+ break;
+ case REQ_Sq: // mdoc(7) "Single Quote"
+ c=process_quote(c,j,"&lsquo;","&rsquo;");
+ 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)
+ {
+ const char *nextbreak = strchr(c, '\n');
+ const 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 QCString 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 QCString 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)
+ QMap<QCString,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 QCString name ( scan_identifier( c ) );
+ if ( name.isEmpty() )
+ {
+ kdDebug(7107) << "EXCEPTION: empty origin string to remove/rename: " << endl;
+ break;
+ }
+ c = skip_till_newline( c );
+ QMap <QCString, 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 QCString 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 QCString name2 ( scan_identifier ( c ) );
+ if ( name2.isEmpty() )
+ {
+ kdDebug(7107) << "EXCEPTION: empty destination to rename number register " << endl;
+ break;
+ }
+ c = skip_till_newline( c );
+ QMap<QCString,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 QCString 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 QCString 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)
+ QMap<QCString,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 QCString 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=qstrlen(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("&nbsp;");
+ 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("&nbsp;"); 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 = qstrlen(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
diff --git a/kioslave/man/man2html.h b/kioslave/man/man2html.h
new file mode 100644
index 000000000..ab672c4e5
--- /dev/null
+++ b/kioslave/man/man2html.h
@@ -0,0 +1,34 @@
+/**
+ * \file man2html.h
+ *
+ * \note Despite that this file is installed publically, it should not be included
+ * \todo ### KDE4: make this file private
+ *
+ */
+
+#include <qcstring.h>
+
+/** call this with the buffer you have */
+void scan_man_page(const char *man_page);
+
+/**
+ * Set the paths to KDE resources
+ *
+ * \param htmlPath Path to the KDE resources, encoded for HTML
+ * \param cssPath Path to the KDE resources, encoded for CSS
+ * \since 3.5
+ *
+ */
+extern void setResourcePath(const QCString& _htmlPath, const QCString& _cssPath);
+
+/** implement this somewhere. It will be called
+ with HTML contents
+*/
+extern void output_real(const char *insert);
+
+/**
+ * called for requested man pages. filename can be a
+ * relative path! Return NULL on errors. The returned
+ * char array is freed by man2html
+ */
+extern char *read_man_page(const char *filename);