text: #include <stdlib.h>
  #include <stdio.h>
  #include <stdarg.h>
  #include <time.h>
  
  #include <qdir.h>
  #include <qfile.h>
  #include <qfileinfo.h>
  #include <qapplication.h>
  #include <qmetaobject.h>
  #include <qdatetime.h>
  #include <QRegularExpression>
  
  #include <QDebug>
  
  #include "klfdefs.h"
  #include "klfqt4common.h"
  
  namespace latex_lib_impl {
  
  	static char __klf_version_string[] = "4.0.0";
  
  	const char * klfVersion() {
  		return __klf_version_string;
  	}
  
  	struct KLFDebugObjectWatcherPrivate {
  		QMap<quintptr, QString> refInfos;
  	};
  
  	KLFDebugObjectWatcher *KLFDebugObjectWatcher::instance = NULL;
  	KLFDebugObjectWatcher *KLFDebugObjectWatcher::getWatcher() {
  		if (instance == NULL)
  			instance = new KLFDebugObjectWatcher;
  		return instance;
  	}
  
  	void KLFDebugObjectWatcher::registerObjectRefInfo(QObject *object, const QString& refinfo) {
  		p->refInfos[(quintptr)object] = refinfo;
  	}
  
  	KLFDebugObjectWatcher::KLFDebugObjectWatcher()
  		: QObject(qApp) {
  		p = new KLFDebugObjectWatcherPrivate;
  	}
  
  	KLFDebugObjectWatcher::~KLFDebugObjectWatcher() {
  		delete p;
  	}
  
  	void KLFDebugObjectWatcher::debugObjectDestroyed(QObject *object) {
  		quintptr obji = (quintptr)object;
  		if (p->refInfos.contains(obji)) {
  			klfDbg(klfFmtCC("Object destroyed: (%s*)%p; object reference name is `%s'",
  				(object ? object->metaObject()->className() : "void"),
  				object, qPrintable(p->refInfos[obji])));
  		}
  		else {
  			klfDbg(klfFmtCC("Object destroyed: (%s*)%p",
  				(object ? object->metaObject()->className() : "void"),
  				object));
  		}
  	}
  
  
  	QByteArray klfShortFuncSignature(const QByteArray& ba_funcname) {
  		QString funcname(ba_funcname);
  		int iSpc, iParen;
  		iParen = funcname.indexOf('(');
  		iSpc = funcname.lastIndexOf(' ', iParen - 2);
  
  		if (iParen == -1 || iSpc > iParen) {
  			qWarning("klfShortFuncSignature('%s'): Signature parse error!", qPrintable(funcname));
  			return ba_funcname;
  		}
  
  		QString f = funcname.mid(iSpc + 1, iParen - (iSpc + 1));
  		QByteArray data = f.s_toLocal8Bit();
  		return data;
  	}
  
  
  	QByteArray klfFmt(const char * fmt, va_list pp) {
  		static const int bufferSize = 8192;
  		char buffer[bufferSize];
  		int len;
  #if defined(_BSD_SOURCE) || _XOPEN_SOURCE >= 500 || defined(_ISOC99_SOURCE)
  		len = vsnprintf(buffer, bufferSize, fmt, pp);
  		if (len >= bufferSize) {
  			qWarning("%s(): output from format string \"%s\" was truncated from %d to %d bytes.",
  				KLF_FUNC_NAME, fmt, len, (bufferSize - 1));
  			len = bufferSize - 1;
  		}
  #else
  		len = vsprintf(buffer, fmt, pp);
  #endif
  
  		if (len < 0) {
  			qWarning("%s(): vs(n)printf() failed for format \"%s\"", KLF_FUNC_NAME, fmt);
  			return QByteArray();
  		}
  
  		QByteArray data;
  		data = QByteArray(buffer, len);
  		return data;
  	}
  
  	QByteArray klfFmt(const char * fmt, ...) {
  		va_list pp;
  		va_start(pp, fmt);
  		QByteArray data = klfFmt(fmt, pp);
  		va_end(pp);
  		return data;
  	}
  
  
  	QString klfTimeOfDay(bool shortfmt) {
  		QDateTime time = QDateTime::currentDateTime();
  		return time.toString();
  	}
  
  #ifdef KLF_DEBUG
  	static int __klf_dbg_block_depth_counter = 0;
  #endif
  
  	KLFDebugBlock::KLFDebugBlock(const QString& blockName) : pBlockName(blockName), pPrintMsg(true) {
  #ifdef KLF_DEBUG
  		qDebug("%s: [%02d]block begin", qPrintable(pBlockName), ++__klf_dbg_block_depth_counter);
  #endif
  	}
  	KLFDebugBlock::KLFDebugBlock(bool printmsg, const QString& blockName) : pBlockName(blockName), pPrintMsg(printmsg) {
  #ifdef KLF_DEBUG
  		if (printmsg)
  			qDebug("%s: [%02d]block begin", qPrintable(pBlockName), ++__klf_dbg_block_depth_counter);
  #endif
  	}
  	KLFDebugBlock::~KLFDebugBlock() {
  #ifdef KLF_DEBUG
  		if (pPrintMsg)
  			qDebug("%s: [%02d]block end", qPrintable(pBlockName), __klf_dbg_block_depth_counter--);
  #endif
  	}
  	KLFDebugBlockTimer::KLFDebugBlockTimer(const QString& blockName) : KLFDebugBlock(false, blockName) {
  #ifdef KLF_DEBUG
  		qDebug("+T:%s: %s: [%02d]block begin", KLF_SHORT_TIME, qPrintable(pBlockName), ++__klf_dbg_block_depth_counter);
  #endif
  	}
  	KLFDebugBlockTimer::~KLFDebugBlockTimer() {
  #ifdef KLF_DEBUG
  		qDebug("+T:%s: %s: [%02d]block end", KLF_SHORT_TIME, qPrintable(pBlockName), __klf_dbg_block_depth_counter--);
  #endif
  	}
  
  	QDebug __klf_dbg_hdr(QDebug dbg, const char * funcname, const char *refinstance, const char * shorttime) {
  		if (shorttime == NULL)
  			return dbg.nospace() << funcname << "():" << refinstance << "\n        ";
  		else
  			return dbg.nospace() << "+T:" << shorttime << ": " << funcname << "():" << refinstance << "\n        ";
  	}
  
  	KLFSysInfo::Os KLFSysInfo::os() {
  #if defined(Q_OS_LINUX)
  		return Linux;
  #elif defined(Q_OS_DARWIN)
  		return MacOsX;
  #elif defined(Q_OS_WIN32)
  		return Win32;
  #else
  		return OtherOs;
  #endif
  	}
  
  	QString KLFSysInfo::osString(Os sysos) {
  		switch (sysos) {
  			case Linux: return QLatin1String("linux");
  			case MacOsX: return QLatin1String("macosx");
  			case Win32: return QLatin1String("win32");
  			case OtherOs: return QString();
  		default:;
  		}
  
  		qWarning("KLFSysInfo::osString: unknown OS: %d", sysos);
  		return QString();
  	}
  
  	QStringList klf_version_suffixes =
  		QStringList() << "a" << "alpha" << "b" << "beta" << "p" << "pre" << "preview" << "RC" << "rc" << "" << "post" << "dev" << "devel";
  
  	static int __klf_version_compare_suffix_words(QString w1, QString w2) {
  		const QStringList& words = klf_version_suffixes;
  		int borderBeforeAfter = words.list_indexOf("");
  		if (borderBeforeAfter < 0) {
  			qWarning("klfVersionCompare: suffix words list doesn't contain \"\"!");
  		}
  
  		int i1 = words.list_indexOf(w1);
  		int i2 = words.list_indexOf(w2);
  		if (i1 == -1 && i2 == -1) {
  			return QString::compare(w1, w2);
  		}
  
  		if (i2 == -1) {
  			return i1 < borderBeforeAfter ? -1 : +1;
  		}
  
  		if (i1 == -1) {
  			return i2 < borderBeforeAfter ? +1 : -1;
  		}
  
  		return i1 - i2;
  	}
  
  	int klfVersionCompare(const QString& v1, const QString& v2) {
  		qDebug("klfVersionCompare(): Comparing versions %s and %s", qPrintable(v1), qPrintable(v2));
  		if (v1 == v2) {
  			return 0;
  		}
  
  		if (v1.isEmpty()) {
  			return -1;
  		}
  
  		if (v2.isEmpty()) {
  			return 1;
  		}
  
  		QRegularExpression rx1(QRegularExpression::anchoredPattern("^(\\d+)(\\.(\\d+)(\\.(\\d+)([a-zA-Z]+\\d*)?)?)?$"));
  		QRegularExpression rx2(rx1);
  
  		QRegularExpressionMatch match1 = rx1.match(v1);
  		if (!(match1.hasMatch())) {
  			qWarning("klfVersionLessThan: Invalid version number format: %s", qPrintable(v1));
  			return -200;
  		}
  
  		QRegularExpressionMatch match2 = rx2.match(v2);
  		if (!(match1.hasMatch())) {
  			qWarning("klfVersionLessThan: Invalid version number format: %s", qPrintable(v2));
  			return -200;
  		}
  
  		int maj1 = match1.captured(1).toInt();
  		int maj2 = match2.captured(1).toInt();
  		if (maj1 != maj2) {
  			return maj1 - maj2;
  		}
  
  		bool hasmin1 = !match1.captured(2).isEmpty();
  		bool hasmin2 = !match2.captured(2).isEmpty();
  		if (!hasmin1 && !hasmin2) {
  			return 0;
  		}
  
  		if (!hasmin1 && hasmin2) {
  			return -1;
  		}
  
  		if (hasmin1 && !hasmin2) {
  			return +1;
  		}
  
  		int min1 = match1.captured(3).toInt();
  		int min2 = match2.captured(3).toInt();
  		if (min1 != min2) {
  			return min1 - min2;
  		}
  
  		bool hasrel1 = !match1.captured(4).isEmpty();
  		bool hasrel2 = !match2.captured(4).isEmpty();
  		if (!hasrel1 && !hasrel2) {
  			return 0;
  		}
  
  		if (!hasrel1 && hasrel2) {
  			return -1;
  		}
  
  		if (hasrel1 && !hasrel2) {
  			return +1;
  		}
  
  		int rel1 = match1.captured(5).toInt();
  		int rel2 = match2.captured(5).toInt();
  		if (rel1 != rel2) {
  			return rel1 - rel2;
  		}
  
  		QString suffix1 = match1.captured(6);
  		QString suffix2 = match2.captured(6);
  
  		if (suffix1 == suffix2) {
  			return 0;
  		}
  
  		QRegularExpression rxs1("^([a-zA-Z]*)(\\d*)$");
  		QRegularExpression rxs2(rxs1);
  
  		QRegularExpressionMatch match3 = rxs1.match(suffix1);
  		QRegularExpressionMatch match4 = rxs2.match(suffix2);
  
  		QString w1 = match3.captured(1);
  		QString w2 = match4.captured(1);
  		QString ns1 = match3.captured(2);
  		QString ns2 = match4.captured(2);
  
  		int cmp = __klf_version_compare_suffix_words(w1, w2);
  		if (cmp != 0) {
  			return cmp;
  		}
  
  		if (ns1.isEmpty()) {
  			if (ns2.isEmpty()) {
  				return 0;
  			}
  
  			return -1;
  		}
  
  		if (ns2.isEmpty()) {
  			return +1;
  		}
  
  		int n1 = ns1.toInt();
  		int n2 = ns2.toInt();
  
  		return n1 - n2;
  	}
  
  	bool klfVersionCompareLessThan(const QString& v1, const QString& v2) {
  		return klfVersionCompare(v1, v2) < 0;
  	}
  
  	static QStringList __search_find_test(
  		const QString& root,
  		const QStringList& pathlist,
  		int level,
  		int limit
  	) {
  		if (limit == 0) {
  			return QStringList();
  		}
  
  		if (limit < 0) {
  			limit = -1;
  		}
  
  		QStringList newpathlist = pathlist;
  		QStringList levelpathlist;
  		int k;
  		for (k = 0; k < level; ++k) {
  			levelpathlist << newpathlist[k];
  		}
  
  		QString flpath = root + levelpathlist.join("/");
  		QFileInfo flinfo(flpath);
  		if (flinfo.isDir()) {
  			QDir d(flpath);
  			QStringList entries;
  			entries = d.entryList(QStringList() << pathlist[level]);
  			QStringList hitlist;
  			for (k = 0; k < (int)entries.size(); ++k) {
  				newpathlist[level] = entries[k];
  				hitlist += __search_find_test(root, newpathlist, level + 1, limit - hitlist.size());
  
  				if (limit >= 0 && (int)hitlist.size() >= limit) {
  					break;
  				}
  			}
  
  			return hitlist;
  		}
  
  		if (flinfo.exists()) {
  			return QStringList() << dir_native_separators(root + pathlist.join("/"));
  		}
  
  		return QStringList();
  	}
  
  	QStringList klfSearchFind(const QString& wildcard_expression, int limit) {
  		klfDbg("looking for " + wildcard_expression);
  
  		QString expr;
  		expr = QDir::fromNativeSeparators(wildcard_expression);
  
  		QStringList pathlist = str_split(expr, "/", false);
  		QString root = "/";
  		static QRegularExpression driveregexp("^[A-Za-z]:$");
  		QRegularExpressionMatch match = driveregexp.match(pathlist[0]);
  
  		if (match.hasMatch()) {
  			root = pathlist[0] + "/";
  			pathlist.pop_front();
  		}
  
  		return __search_find_test(root, pathlist, 0, limit);
  	}
  
  	QString klfSearchPath(const QString& programName, const QString& extra_path) {
  		static const QString PATH = getenv("PATH");
  		static const QString pathsep = QString("") + KLF_PATH_SEP;
  
  		QString path = PATH;
  		if (!extra_path.isEmpty()) {
  			path = extra_path + pathsep + path;
  		}
  
  		const QStringList paths = str_split(path, pathsep, true);
  		QString test;
  		int k, j;
  		for (k = 0; k < (int)paths.size(); ++k) {
  			klfDbg("searching in " + paths[k]);
  			QStringList hits = klfSearchFind(paths[k] + "/" + programName);
  			klfDbg("\t...resulting in hits = " + hits.join(" ;; "));
  
  			for (j = 0; j < (int)hits.size(); ++j) {
  				if (QFileInfo(hits[j]).isExecutable()) {
  					klfDbg("\tFound definitive (executable) hit at " + hits[j]);
  					return hits[j];
  				}
  			}
  		}
  		return QString();
  	}
  
  }