summaryrefslogtreecommitdiffstats
path: root/addon
diff options
context:
space:
mode:
authorDimitri van Heesch <dimitri@stack.nl>2012-12-26 15:59:17 (GMT)
committerDimitri van Heesch <dimitri@stack.nl>2012-12-26 15:59:17 (GMT)
commit48f4de5c47d55b6622b6fdc9b5c288e19d5692f9 (patch)
tree629c4681a5158d26512b815623754b33165d8d23 /addon
parentfee4053bd3dd075a2dd2cba4da8166ec5307eadd (diff)
downloadDoxygen-48f4de5c47d55b6622b6fdc9b5c288e19d5692f9.zip
Doxygen-48f4de5c47d55b6622b6fdc9b5c288e19d5692f9.tar.gz
Doxygen-48f4de5c47d55b6622b6fdc9b5c288e19d5692f9.tar.bz2
Release-1.8.3
Diffstat (limited to 'addon')
-rw-r--r--addon/doxyapp/doxyapp.cpp13
-rw-r--r--addon/doxyapp/doxyapp.pro.in2
-rw-r--r--addon/doxysearch/Makefile.in34
-rw-r--r--addon/doxysearch/doxyindexer.cpp377
-rw-r--r--addon/doxysearch/doxyindexer.pro.in12
-rw-r--r--addon/doxysearch/doxysearch.cpp434
-rw-r--r--addon/doxysearch/doxysearch.pro.in12
7 files changed, 881 insertions, 3 deletions
diff --git a/addon/doxyapp/doxyapp.cpp b/addon/doxyapp/doxyapp.cpp
index ffee3c7..521d1cc 100644
--- a/addon/doxyapp/doxyapp.cpp
+++ b/addon/doxyapp/doxyapp.cpp
@@ -30,6 +30,13 @@
#include "doxygen.h"
#include "outputgen.h"
#include "parserintf.h"
+#include "classdef.h"
+#include "namespacedef.h"
+#include "filedef.h"
+#include "util.h"
+#include "classlist.h"
+#include "config.h"
+#include "filename.h"
class XRefDummyCodeGenerator : public CodeOutputInterface
{
@@ -41,14 +48,16 @@ class XRefDummyCodeGenerator : public CodeOutputInterface
// and cross-linked version of the source code, but who needs that anyway ;-)
void codify(const char *) {}
void writeCodeLink(const char *,const char *,const char *,const char *,const char *) {}
- void startCodeLine() {}
+ void writeLineNumber(const char *,const char *,const char *,int) {}
+ void startCodeLine(bool) {}
void endCodeLine() {}
void startCodeAnchor(const char *) {}
void endCodeAnchor() {}
void startFontClass(const char *) {}
void endFontClass() {}
void writeCodeAnchor(const char *) {}
- void writeLineNumber(const char *,const char *,const char *,int) {}
+ void setCurrentDoc(Definition *,const char *,bool) {}
+ void addWord(const char *,bool) {}
// here we are presented with the symbols found by the code parser
void linkableSymbol(int l, const char *sym,Definition *symDef,Definition *context)
diff --git a/addon/doxyapp/doxyapp.pro.in b/addon/doxyapp/doxyapp.pro.in
index 68fea3d..f83fe30 100644
--- a/addon/doxyapp/doxyapp.pro.in
+++ b/addon/doxyapp/doxyapp.pro.in
@@ -2,7 +2,7 @@ TEMPLATE = app.t
CONFIG = console warn_on debug
HEADERS =
SOURCES = doxyapp.cpp
-LIBS += -L../../lib -L../../lib -ldoxygen -lqtools -lmd5 -ldoxycfg -lpng
+LIBS += -L../../lib -L../../lib -ldoxygen -lqtools -lmd5 -ldoxycfg
DESTDIR =
OBJECTS_DIR = ../../objects
TARGET = ../../bin/doxyapp
diff --git a/addon/doxysearch/Makefile.in b/addon/doxysearch/Makefile.in
new file mode 100644
index 0000000..3159e22
--- /dev/null
+++ b/addon/doxysearch/Makefile.in
@@ -0,0 +1,34 @@
+
+all clean depend: Makefile.doxysearch Makefile.doxyindexer
+ $(MAKE) -f Makefile.doxysearch $@
+ $(MAKE) -f Makefile.doxyindexer $@
+
+distclean: clean
+ $(RM) -rf Makefile doxysearch.pro Makefile.doxysearch
+ $(RM) -rf Makefile doxyindexer.pro Makefile.doxyindexer
+
+tmake:
+ $(ENV) $(PERL) $(TMAKE) doxysearch.pro >Makefile.doxysearch
+ $(ENV) $(PERL) $(TMAKE) doxyindexer.pro >Makefile.doxyindexer
+
+strip:
+ strip doxysearch
+
+Makefile.doxysearch: doxysearch.pro
+ $(ENV) $(PERL) $(TMAKE) doxysearch.pro >Makefile.doxysearch
+
+Makefile.doxyindexer: doxyindexer.pro
+ $(ENV) $(PERL) $(TMAKE) doxyindexer.pro >Makefile.doxyindexer
+
+install:
+ $(INSTTOOL) -d $(INSTALL)/bin
+ $(INSTTOOL) -m 755 ../../bin/doxysearch.cgi $(INSTALL)/bin
+ $(INSTTOOL) -m 755 ../../bin/doxyindexer $(INSTALL)/bin
+ $(INSTTOOL) -d $(INSTALL)/$(MAN1DIR)
+ cat ../../doc/doxyindexer.1 | sed -e "s/DATE/$(DATE)/g" -e "s/VERSION/$(VERSION)/g" > doxyindexer.1
+ $(INSTTOOL) -m 644 doxyindexer.1 $(INSTALL)/$(MAN1DIR)/doxyindexer.1
+ rm doxyindexer.1
+ cat ../../doc/doxysearch.1 | sed -e "s/DATE/$(DATE)/g" -e "s/VERSION/$(VERSION)/g" > doxysearch.1
+ $(INSTTOOL) -m 644 doxysearch.1 $(INSTALL)/$(MAN1DIR)/doxysearch.1
+ rm doxysearch.1
+
diff --git a/addon/doxysearch/doxyindexer.cpp b/addon/doxysearch/doxyindexer.cpp
new file mode 100644
index 0000000..c809e0e
--- /dev/null
+++ b/addon/doxysearch/doxyindexer.cpp
@@ -0,0 +1,377 @@
+/******************************************************************************
+ *
+ * Copyright (C) 1997-2012 by Dimitri van Heesch.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation under the terms of the GNU General Public License is hereby
+ * granted. No representations are made about the suitability of this software
+ * for any purpose. It is provided "as is" without express or implied warranty.
+ * See the GNU General Public License for more details.
+ *
+ * Documents produced by Doxygen are derivative works derived from the
+ * input used in their production; they are not affected by this license.
+ *
+ */
+
+// STL includes
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
+#include <string>
+#include <algorithm>
+#include <sstream>
+
+// Qtools includes
+#include <qregexp.h>
+#include <qxml.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+
+// Xapian include
+#include <xapian.h>
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+static char pathSep = '\\';
+#else
+static char pathSep = '/';
+#endif
+
+/** trims \a whitespace characters from the start and end of string \a str. */
+static std::string trim(const std::string& str,
+ const std::string& whitespace = " \t")
+{
+ size_t strBegin = str.find_first_not_of(whitespace);
+ if (strBegin == std::string::npos)
+ return ""; // no content
+
+ size_t strEnd = str.find_last_not_of(whitespace);
+ int strRange = strEnd - strBegin + 1;
+
+ return str.substr(strBegin, strRange);
+}
+
+/** trims \a whitespace from start and end and replace occurrences of
+ * \a whitespace with \a fill.
+ */
+static std::string reduce(const std::string& str,
+ const std::string& fill = " ",
+ const std::string& whitespace = " \t")
+{
+ // trim first
+ std::string result = trim(str, whitespace);
+
+ // replace sub ranges
+ size_t beginSpace = result.find_first_of(whitespace);
+ while (beginSpace != std::string::npos)
+ {
+ size_t endSpace = result.find_first_not_of(whitespace, beginSpace);
+ int range = endSpace - beginSpace;
+
+ result.replace(beginSpace, range, fill);
+
+ size_t newStart = beginSpace + fill.length();
+ beginSpace = result.find_first_of(whitespace, newStart);
+ }
+
+ return result;
+}
+
+/** Adds all words in \a s to document \a doc with weight \a wfd */
+static void addWords(const std::string &s,Xapian::Document &doc,int wfd)
+{
+ std::istringstream iss(s);
+ std::istream_iterator<std::string> begin(iss),end,it;
+ for (it=begin;it!=end;++it)
+ {
+ std::string word = *it;
+ std::string lword = word;
+ std::transform(lword.begin(), lword.end(), lword.begin(), ::tolower);
+ doc.add_term(word,wfd);
+ if (lword!=word)
+ {
+ doc.add_term(lword,wfd);
+ }
+ }
+}
+
+/** Adds all identifiers in \a s to document \a doc with weight \a wfd */
+static void addIdentifiers(const std::string &s,Xapian::Document &doc,int wfd)
+{
+ QRegExp re("[A-Z_a-z][A-Z_a-z0-9]*");
+ int i,l,p=0;
+ QCString qs = s.c_str();
+ while ((i=re.match(qs,p,&l))!=-1)
+ {
+ doc.add_term(qs.mid(p,i-p).data(),wfd);
+ p=i+l;
+ }
+}
+
+/** Replaces all occurrences of \a old with \a repl in string \a str */
+static void replace_all(std::string& str, const std::string& old, const std::string& repl)
+{
+ size_t pos = 0;
+ while ((pos = str.find(old, pos)) != std::string::npos)
+ {
+ str.replace(pos, old.length(), repl);
+ pos += repl.length();
+ }
+}
+
+/** Replaces all XML entities in \a s with their unescaped representation */
+static std::string unescapeXmlEntities(const std::string &s)
+{
+ std::string result=s;
+ replace_all(result,"&gt;",">");
+ replace_all(result,"&lt;","<");
+ replace_all(result,"&apos;","'");
+ replace_all(result,"&quot;","\"");
+ replace_all(result,"&amp;","&");
+ return result;
+}
+
+/** This class is a wrapper around SAX style XML parser, which
+ * parses the file without first building a DOM tree in memory.
+ */
+class XMLContentHandler : public QXmlDefaultHandler
+{
+ public:
+ /** Handler for parsing XML data */
+ XMLContentHandler(const QString &path)
+ : m_db((path+"doxysearch.db").utf8().data(),Xapian::DB_CREATE_OR_OVERWRITE),
+ m_stemmer("english")
+ {
+ m_curFieldName = UnknownField;
+ m_indexer.set_stemmer(m_stemmer);
+ m_indexer.set_document(m_doc);
+ }
+
+ /** Free data handler */
+ ~XMLContentHandler()
+ {
+ m_db.commit();
+ }
+
+ private:
+ enum FieldNames
+ {
+ UnknownField = 0,
+ TypeField = 1,
+ NameField = 2,
+ ArgsField = 3,
+ TagField = 4,
+ UrlField = 5,
+ KeywordField = 6,
+ TextField = 7
+ };
+
+ /** Handler for a start tag. Called for <doc> and <field> tags */
+ bool startElement(const QString &, const QString &,
+ const QString &name, const QXmlAttributes &attrib)
+ {
+ m_data="";
+ if (name=="field")
+ {
+ QString fieldName = attrib.value("name");
+ if (fieldName=="type") m_curFieldName=TypeField;
+ else if (fieldName=="name") m_curFieldName=NameField;
+ else if (fieldName=="args") m_curFieldName=ArgsField;
+ else if (fieldName=="tag") m_curFieldName=TagField;
+ else if (fieldName=="url") m_curFieldName=UrlField;
+ else if (fieldName=="keywords") m_curFieldName=KeywordField;
+ else if (fieldName=="text") m_curFieldName=TextField;
+ else m_curFieldName=UnknownField;
+ }
+ return TRUE;
+ }
+
+ /** Handler for an end tag. Called for </doc> and </field> tags */
+ bool endElement(const QString &, const QString &, const QString &name)
+ {
+ if (name=="doc") // </doc>
+ {
+ std::string term = m_doc.get_value(NameField);
+ std::string partTerm;
+ size_t pos = term.rfind("::");
+ if (pos!=std::string::npos)
+ {
+ partTerm = term.substr(pos+2);
+ }
+ if (m_doc.get_value(TypeField)=="class" ||
+ m_doc.get_value(TypeField)=="file" ||
+ m_doc.get_value(TypeField)=="namespace") // containers get highest prio
+ {
+ m_doc.add_term(term,1000);
+ if (!partTerm.empty())
+ {
+ m_doc.add_term(partTerm,500);
+ }
+ }
+ else // members and others get lower prio
+ {
+ m_doc.add_term(m_doc.get_value(NameField),100);
+ if (!partTerm.empty())
+ {
+ m_doc.add_term(partTerm,50);
+ }
+ }
+ m_db.add_document(m_doc);
+ m_doc.clear_values();
+ m_doc.clear_terms();
+ }
+ else if (name=="field" && m_curFieldName!=UnknownField) // </field>
+ {
+ // strip whitespace from m_data
+ m_data = reduce(m_data);
+ // replace XML entities
+ m_data = unescapeXmlEntities(m_data);
+ // add data to the document
+ m_doc.add_value(m_curFieldName,m_data);
+ switch (m_curFieldName)
+ {
+ case TypeField:
+ case NameField:
+ case TagField:
+ case UrlField:
+ // meta data that is not searchable
+ break;
+ case KeywordField:
+ addWords(m_data,m_doc,50);
+ break;
+ case ArgsField:
+ addIdentifiers(m_data,m_doc,10);
+ break;
+ case TextField:
+ addWords(m_data,m_doc,2);
+ break;
+ default:
+ break;
+ }
+ m_data="";
+ m_curFieldName=UnknownField;
+ }
+ // reset m_data
+ return TRUE;
+ }
+
+ /** Handler for inline text */
+ bool characters(const QString& ch)
+ {
+ m_data += ch.utf8();
+ return TRUE;
+ }
+
+ // internal state
+ Xapian::WritableDatabase m_db;
+ Xapian::Document m_doc;
+ Xapian::TermGenerator m_indexer;
+ Xapian::Stem m_stemmer;
+ std::string m_data;
+ FieldNames m_curFieldName;
+};
+
+/** Class for handling error during XML parsing */
+class XMLErrorHandler : public QXmlErrorHandler
+{
+ public:
+ virtual ~XMLErrorHandler() {}
+ bool warning( const QXmlParseException & )
+ {
+ return FALSE;
+ }
+ bool error( const QXmlParseException & )
+ {
+ return FALSE;
+ }
+ bool fatalError( const QXmlParseException &exception )
+ {
+ std::cerr << "Fatal error at line " << exception.lineNumber()
+ << " column " << exception.columnNumber() << ": "
+ << exception.message().utf8() << std::endl;
+ return FALSE;
+ }
+ QString errorString() { return ""; }
+
+ private:
+ QString errorMsg;
+};
+
+static void usage(const char *name)
+{
+ std::cerr << "Usage: " << name << " [-o output_dir] searchdata.xml [searchdata2.xml ...]" << std::endl;
+ exit(1);
+}
+
+/** main function to index data */
+int main(int argc,const char **argv)
+{
+ if (argc<2)
+ {
+ usage(argv[0]);
+ }
+ QString outputDir;
+ for (int i=1;i<argc;i++)
+ {
+ if (std::string(argv[i])=="-o")
+ {
+ if (i>=argc-1)
+ {
+ std::cerr << "Error: missing parameter for -o option" << std::endl;
+ usage(argv[0]);
+ }
+ else
+ {
+ i++;
+ outputDir=argv[i];
+ QFileInfo fi(outputDir);
+ if (!fi.exists() || !fi.isDir())
+ {
+ std::cerr << "Error: specified output directory does not exist!" << std::endl;
+ usage(argv[0]);
+ }
+ }
+ }
+ else if (std::string(argv[i])=="-h" || std::string(argv[i])=="--help")
+ {
+ usage(argv[0]);
+ }
+ }
+
+ try
+ {
+ if (!outputDir.isEmpty() && outputDir.at(outputDir.length()-1)!=pathSep)
+ {
+ outputDir+=pathSep;
+ }
+ XMLContentHandler handler(outputDir);
+ XMLErrorHandler errorHandler;
+ for (int i=1;i<argc;i++)
+ {
+ if (std::string(argv[i])=="-o")
+ {
+ i++;
+ }
+ else
+ {
+ QString xmlFileName = argv[i];
+ std::cout << "Processing " << xmlFileName.utf8() << "..." << std::endl;
+ QFile xmlFile(xmlFileName);
+ QXmlInputSource source(xmlFile);
+ QXmlSimpleReader reader;
+ reader.setContentHandler(&handler);
+ reader.setErrorHandler(&errorHandler);
+ reader.parse(source);
+ }
+ }
+ }
+ catch(const Xapian::Error &e)
+ {
+ std::cerr << "Caught exception: " << e.get_description() << std::endl;
+ }
+ catch(...)
+ {
+ std::cerr << "Caught an unknown exception" << std::endl;
+ }
+
+ return 0;
+}
diff --git a/addon/doxysearch/doxyindexer.pro.in b/addon/doxysearch/doxyindexer.pro.in
new file mode 100644
index 0000000..7286df8
--- /dev/null
+++ b/addon/doxysearch/doxyindexer.pro.in
@@ -0,0 +1,12 @@
+TEMPLATE = app.t
+CONFIG = console warn_on static release
+HEADERS =
+SOURCES = doxyindexer.cpp
+LIBS += -lxapian -lqtools -L../../lib
+DESTDIR =
+OBJECTS_DIR = ../../objects
+TARGET = ../../bin/doxyindexer
+INCLUDEPATH += ../../qtools
+DEPENDPATH +=
+TARGETDEPS =
+
diff --git a/addon/doxysearch/doxysearch.cpp b/addon/doxysearch/doxysearch.cpp
new file mode 100644
index 0000000..7b90c82
--- /dev/null
+++ b/addon/doxysearch/doxysearch.cpp
@@ -0,0 +1,434 @@
+/******************************************************************************
+ *
+ * Copyright (C) 1997-2012 by Dimitri van Heesch.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation under the terms of the GNU General Public License is hereby
+ * granted. No representations are made about the suitability of this software
+ * for any purpose. It is provided "as is" without express or implied warranty.
+ * See the GNU General Public License for more details.
+ *
+ * Documents produced by Doxygen are derivative works derived from the
+ * input used in their production; they are not affected by this license.
+ *
+ */
+
+// STL includes
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+#include <vector>
+#include <sstream>
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <algorithm>
+
+// Xapian includes
+#include <xapian.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <sys/stat.h>
+#endif
+
+#define FIELD_TYPE 1
+#define FIELD_NAME 2
+#define FIELD_ARGS 3
+#define FIELD_TAG 4
+#define FIELD_URL 5
+#define FIELD_KEYW 6
+#define FIELD_DOC 7
+
+#define HEX2DEC(x) (((x)>='0' && (x)<='9')?((x)-'0'):\
+ ((x)>='a' && (x)<='f')?((x)-'a'+10):\
+ ((x)>='A' && (x)<='F')?((x)-'A'+10):-1)
+
+
+bool dirExists(const std::string& dirName)
+{
+#ifdef _WIN32
+ DWORD ftyp = GetFileAttributesA(dirName.c_str());
+ if (ftyp == INVALID_FILE_ATTRIBUTES)
+ return false; //something is wrong with your path!
+
+ if (ftyp & FILE_ATTRIBUTE_DIRECTORY)
+ return true; // this is a directory!
+#else
+ struct stat sb;
+
+ if (stat(dirName.c_str(), &sb)==0 && S_ISDIR(sb.st_mode))
+ {
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+
+/** decodes a URI encoded string into a normal string. */
+static std::string uriDecode(const std::string & sSrc)
+{
+ // Note from RFC1630: "Sequences which start with a percent
+ // sign but are not followed by two hexadecimal characters
+ // (0-9, A-F) are reserved for future extension"
+
+ const unsigned char * pSrc = (const unsigned char *)sSrc.c_str();
+ const int SRC_LEN = sSrc.length();
+ const unsigned char * const SRC_END = pSrc + SRC_LEN;
+ // last decodable '%'
+ const unsigned char * const SRC_LAST_DEC = SRC_END - 2;
+
+ char * const pStart = new char[SRC_LEN];
+ char * pEnd = pStart;
+
+ while (pSrc < SRC_LAST_DEC)
+ {
+ if (*pSrc == '%') // replace %2A with corresponding ASCII character
+ {
+ char dec1, dec2;
+ unsigned char c1=*(pSrc+1);
+ unsigned char c2=*(pSrc+2);
+ if (-1 != (dec1 = HEX2DEC(c1))
+ && -1 != (dec2 = HEX2DEC(c2)))
+ {
+ *pEnd++ = (dec1 << 4) + dec2;
+ pSrc += 3;
+ continue;
+ }
+ }
+ else if (*pSrc == '+') // replace '+' with space
+ {
+ *pEnd++ = ' '; pSrc++;
+ continue;
+ }
+ *pEnd++ = *pSrc++;
+ }
+
+ // the last 2- chars
+ while (pSrc < SRC_END) *pEnd++ = *pSrc++;
+
+ std::string sResult(pStart, pEnd);
+ delete [] pStart;
+ return sResult;
+}
+
+/** return list of strings that result when splitting \a s using
+ * delimeter \a delim
+ */
+static std::vector<std::string> split(const std::string &s, char delim)
+{
+ std::vector<std::string> elems;
+ std::stringstream ss(s);
+ std::string item;
+ while (getline(ss, item, delim)) elems.push_back(item);
+ return elems;
+}
+
+/** Read type T from string \a s */
+template<class T>
+T fromString(const std::string& s)
+{
+ std::istringstream stream (s);
+ T t;
+ stream >> t;
+ return t;
+}
+
+/** Class that holds the startin position of a word */
+struct WordPosition
+{
+ WordPosition(int s,int i) : start(s), index(i) {}
+ int start;
+ int index;
+};
+
+/** Class representing the '<' operator for WordPosition objects based on position. */
+struct WordPosition_less
+{
+ bool operator()(const WordPosition &p1,const WordPosition &p2)
+ {
+ return p1.start<p2.start;
+ }
+};
+
+/** Class that holds a text fragment */
+struct Fragment
+{
+ Fragment(const std::string &t,int occ) : text(t), occurrences(occ) {}
+ std::string text;
+ int occurrences;
+};
+
+/** Class representing the '>' operator for Fragment objects based on occurrence. */
+struct Fragment_greater
+{
+ bool operator()(const Fragment &p1,const Fragment &p2)
+ {
+ return p1.occurrences>p2.occurrences;
+ }
+};
+
+/** Class representing a range within a string */
+struct Range
+{
+ Range(int s,int e) : start(s), end(e) {}
+ int start;
+ int end;
+};
+
+/** Returns true if [start..start+len] is inside one of the \a ranges. */
+static bool insideRange(const std::vector<Range> &ranges,int start,int len)
+{
+ for (std::vector<Range>::const_iterator it = ranges.begin();
+ it!=ranges.end(); ++it
+ )
+ {
+ Range r = *it;
+ if (start>=r.start && start+len<r.end)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+/** Returns a list of text \a fragments from \a s containing one or
+ * more \a words. The list is sorted occording to the
+ * number of occurrences of words within the fragment.
+ */
+static void highlighter(const std::string &s,
+ const std::vector<std::string> &words,
+ std::vector<Fragment> &fragments)
+{
+ const std::string spanStart="<span class=\"hl\">";
+ const std::string spanEnd="</span>";
+ const std::string dots="...";
+ const int fragLen = 60;
+ int sl=s.length();
+
+ // find positions of words in s
+ size_t j=0;
+ std::vector<WordPosition> positions;
+ for (std::vector<std::string>::const_iterator it=words.begin();
+ it!=words.end();
+ ++it,++j
+ )
+ {
+ int pos=0;
+ size_t i;
+ std::string word = *it;
+ while ((i=s.find(word,pos))!=std::string::npos)
+ {
+ positions.push_back(WordPosition(i,j));
+ pos=i+word.length();
+ }
+ }
+ // sort on position
+ std::sort(positions.begin(),positions.end(),WordPosition_less());
+ // get fragments around words
+ std::vector<Range> ranges;
+ for (std::vector<WordPosition>::const_iterator it=positions.begin();
+ it!=positions.end();
+ ++it)
+ {
+ WordPosition wp = *it;
+ std::string w = words[wp.index];
+ int i=wp.start;
+ int wl=w.length();
+ if (!insideRange(ranges,i,wl))
+ {
+ if (wl>fragLen)
+ {
+ fragments.push_back(Fragment(spanStart+w+spanEnd,1));
+ ranges.push_back(Range(i,i+wl));
+ }
+ else
+ {
+ std::string startFragment,endFragment;
+ int bi=i-(fragLen-wl)/2;
+ int ei=i+wl+(fragLen-wl)/2;
+ int occ=0;
+ if (bi<0) { ei-=bi; bi=0; } else startFragment=dots;
+ if (ei>sl) { ei=sl; } else endFragment=dots;
+ while (bi>0 && !isspace(s[bi])) bi--; // round to start of the word
+ while (ei<sl && !isspace(s[ei])) ei++; // round to end of the word
+ // highlight any word in s between indexes bi and ei
+ std::string fragment=startFragment;
+ int pos=bi;
+ for (std::vector<WordPosition>::const_iterator it2=positions.begin();
+ it2!=positions.end();
+ ++it2)
+ {
+ WordPosition wp2 = *it2;
+ std::string w2 = words[wp2.index];
+ int wl2 = w2.length();
+ if (wp2.start>=bi && wp2.start+wl2<=ei) // word is inside the range!
+ {
+ fragment+=s.substr(pos,wp2.start-pos)+
+ spanStart+
+ s.substr(wp2.start,wl2)+
+ spanEnd;
+ pos=wp2.start+wl2;
+ occ++;
+ }
+ }
+ fragment+=s.substr(pos,ei-pos)+endFragment;
+ fragments.push_back(Fragment(fragment,occ));
+ ranges.push_back(Range(bi,ei));
+ }
+ }
+ }
+ std::sort(fragments.begin(),fragments.end(),Fragment_greater());
+}
+
+/** Escapes a string such that is can be included in a JSON structure */
+static std::string escapeString(const std::string &s)
+{
+ std::stringstream dst;
+ for (unsigned int i=0;i<s.length();i++)
+ {
+ char ch = s[i];
+ switch (ch)
+ {
+ case '\"': dst << "\\\""; break;
+ default: dst << ch; break;
+ }
+ }
+ return dst.str();
+}
+
+static void showError(const std::string &callback,const std::string &error)
+{
+ std::cout << callback << "({\"error\":\"" << error << "\"})";
+ exit(0);
+}
+
+/** Main routine */
+int main(int argc,char **argv)
+{
+ // process inputs that were passed to us via QUERY_STRING
+ std::cout << "Content-Type:application/javascript;charset=utf-8\r\n\n";
+ std::string callback;
+ try
+ {
+ // get input parameters
+ const char *queryEnv = getenv("QUERY_STRING");
+ std::string queryString;
+ if (queryEnv)
+ {
+ queryString = queryEnv;
+ }
+ else if (argc>=2)
+ {
+ queryString = argv[1];
+ }
+ else
+ {
+ std::cout << "No input!\n";
+ exit(1);
+ }
+
+ // parse query string
+ std::vector<std::string> parts = split(queryString,'&');
+ std::string searchFor,callback;
+ int num=1,page=0;
+ for (std::vector<std::string>::const_iterator it=parts.begin();it!=parts.end();++it)
+ {
+ std::vector<std::string> kv = split(*it,'=');
+ if (kv.size()==2)
+ {
+ std::string val = uriDecode(kv[1]);
+ if (kv[0]=="q") searchFor = val;
+ else if (kv[0]=="n") num = fromString<int>(val);
+ else if (kv[0]=="p") page = fromString<int>(val);
+ else if (kv[0]=="cb") callback = val;
+ }
+ }
+
+ std::string indexDir = "doxysearch.db";
+
+ if (queryString=="test") // user test
+ {
+ bool dbOk = dirExists(indexDir);
+ if (dbOk)
+ {
+ std::cout << "Test successful.";
+ }
+ else
+ {
+ std::cout << "Test failed: cannot find search index " << indexDir;
+ }
+ exit(0);
+ }
+
+ // create query
+ Xapian::Database db(indexDir);
+ Xapian::Enquire enquire(db);
+ Xapian::Query query;
+ std::vector<std::string> words = split(searchFor,' ');
+ for (std::vector<std::string>::const_iterator it=words.begin();it!=words.end();++it)
+ {
+ query = Xapian::Query(Xapian::Query::OP_OR,query,Xapian::Query(*it));
+ }
+ enquire.set_query(query);
+
+ // get results
+ Xapian::MSet matches = enquire.get_mset(page*num,num);
+ unsigned int hits = matches.get_matches_estimated();
+ unsigned int offset = page*num;
+ unsigned int pages = num>0 ? (hits+num-1)/num : 0;
+ if (offset>hits) offset=hits;
+ if (offset+num>hits) num=hits-offset;
+
+ // write results as JSONP
+ std::cout << callback.c_str() << "(";
+ std::cout << "{" << std::endl
+ << " \"hits\":" << hits << "," << std::endl
+ << " \"first\":" << offset << "," << std::endl
+ << " \"count\":" << num << "," << std::endl
+ << " \"page\":" << page << "," << std::endl
+ << " \"pages\":" << pages << "," << std::endl
+ << " \"query\": \"" << escapeString(searchFor) << "\"," << std::endl
+ << " \"items\":[" << std::endl;
+ // foreach search result
+ unsigned int o = offset;
+ for (Xapian::MSetIterator i = matches.begin(); i != matches.end(); ++i,++o)
+ {
+ std::vector<Fragment> hl;
+ Xapian::Document doc = i.get_document();
+ highlighter(doc.get_value(FIELD_DOC),words,hl);
+ std::cout << " {\"type\": \"" << doc.get_value(FIELD_TYPE) << "\"," << std::endl
+ << " \"name\": \"" << doc.get_value(FIELD_NAME) << doc.get_value(FIELD_ARGS) << "\"," << std::endl
+ << " \"tag\": \"" << doc.get_value(FIELD_TAG) << "\"," << std::endl
+ << " \"url\": \"" << doc.get_value(FIELD_URL) << "\"," << std::endl;
+ std::cout << " \"fragments\":[" << std::endl;
+ int c=0;
+ bool first=true;
+ for (std::vector<Fragment>::const_iterator it = hl.begin();it!=hl.end() && c<3;++it,++c)
+ {
+ if (!first) std::cout << "," << std::endl;
+ std::cout << " \"" << escapeString((*it).text) << "\"";
+ first=false;
+ }
+ if (!first) std::cout << std::endl;
+ std::cout << " ]" << std::endl;
+ std::cout << " }";
+ if (o<offset+num-1) std::cout << ",";
+ std::cout << std::endl;
+ }
+ std::cout << " ]" << std::endl << "})" << std::endl;
+ }
+ catch (const Xapian::Error &e) // Xapian exception
+ {
+ showError(callback,e.get_description());
+ }
+ catch (...) // Any other exception
+ {
+ showError(callback,"Unknown Exception!");
+ exit(1);
+ }
+ return 0;
+}
diff --git a/addon/doxysearch/doxysearch.pro.in b/addon/doxysearch/doxysearch.pro.in
new file mode 100644
index 0000000..fce6d82
--- /dev/null
+++ b/addon/doxysearch/doxysearch.pro.in
@@ -0,0 +1,12 @@
+TEMPLATE = app.t
+CONFIG = console warn_on debug
+HEADERS =
+SOURCES = doxysearch.cpp
+LIBS += -lxapian
+DESTDIR =
+OBJECTS_DIR = ../../objects
+TARGET = ../../bin/doxysearch.cgi
+INCLUDEPATH +=
+DEPENDPATH +=
+TARGETDEPS =
+