From 48f4de5c47d55b6622b6fdc9b5c288e19d5692f9 Mon Sep 17 00:00:00 2001 From: Dimitri van Heesch Date: Wed, 26 Dec 2012 15:59:17 +0000 Subject: Release-1.8.3 --- INSTALL | 4 +- Makefile.in | 27 +- README | 4 +- addon/doxyapp/doxyapp.cpp | 13 +- addon/doxyapp/doxyapp.pro.in | 2 +- addon/doxysearch/Makefile.in | 34 +++ addon/doxysearch/doxyindexer.cpp | 377 +++++++++++++++++++++++++++ addon/doxysearch/doxyindexer.pro.in | 12 + addon/doxysearch/doxysearch.cpp | 434 +++++++++++++++++++++++++++++++ addon/doxysearch/doxysearch.pro.in | 12 + configure | 61 ++++- doc/Doxyfile | 2 +- doc/commands.doc | 43 +-- doc/config.doc | 128 ++++++--- doc/doxygen_manual.css | 2 +- doc/doxygen_manual.tex | 1 + doc/doxyindexer.1 | 17 ++ doc/doxysearch.1 | 11 + doc/extsearch.doc | 320 +++++++++++++++++++++++ doc/faq.doc | 2 +- doc/index.doc | 1 + doc/install.doc | 14 +- doc/language.doc | 2 +- doc/searching.doc | 44 +++- doc/translator_report.txt | 2 +- packages/rpm/doxygen.spec.in | 29 ++- qtools/Doxyfile | 13 +- src/classdef.cpp | 17 +- src/code.l | 6 +- src/commentcnv.l | 47 ++-- src/commentscan.l | 99 ++++--- src/condparser.cpp | 11 +- src/config.l | 21 +- src/config.xml | 42 ++- src/configoptions.cpp | 58 ++++- src/definition.cpp | 7 +- src/doctokenizer.l | 2 +- src/doxygen.cpp | 80 +----- src/doxygen.css | 8 +- src/doxygen_css.h | 8 +- src/eclipsehelp.cpp | 50 +++- src/eclipsehelp.h | 1 + src/extsearch.js | 129 +++++++++ src/extsearch_js.h | 129 +++++++++ src/filedef.cpp | 2 + src/groupdef.cpp | 2 +- src/htmlgen.cpp | 196 +++++++++++--- src/htmlgen.h | 5 + src/htmlhelp.cpp | 47 ---- src/latexdocvisitor.cpp | 4 +- src/latexgen.h | 3 + src/libdoxygen.pro.in | 1 + src/libdoxygen.t.in | 3 + src/mangen.h | 2 + src/markdown.cpp | 7 +- src/memberdef.cpp | 30 ++- src/navtree.js | 49 +--- src/navtree_js.h | 49 +--- src/outputgen.h | 2 + src/outputlist.cpp | 1 + src/outputlist.h | 6 + src/portable.cpp | 19 ++ src/portable.h | 2 +- src/pre.l | 25 +- src/rtfgen.h | 2 + src/scanner.l | 32 ++- src/search.css | 33 +++ src/search_css.h | 33 +++ src/search_functions.php | 8 - src/search_functions_php.h | 8 - src/searchindex.cpp | 37 ++- src/store.cpp | 26 +- src/tagreader.cpp | 12 +- src/tclscanner.l | 4 +- src/translator_cn.h | 2 +- src/util.cpp | 1 - src/vhdldocgen.cpp | 2 - src/xmlgen.cpp | 6 + tmake/lib/linux-64/tmake.conf | 2 +- tmake/lib/linux-g++/tmake.conf | 2 +- tmake/lib/m68k-atari-mint-g++/tmake.conf | 2 +- winbuild/Doxygen.sln | 22 ++ winbuild/doxyindexer.vcproj | 355 +++++++++++++++++++++++++ winbuild/doxysearch.vcproj | 351 +++++++++++++++++++++++++ 84 files changed, 3177 insertions(+), 544 deletions(-) create mode 100644 addon/doxysearch/Makefile.in create mode 100644 addon/doxysearch/doxyindexer.cpp create mode 100644 addon/doxysearch/doxyindexer.pro.in create mode 100644 addon/doxysearch/doxysearch.cpp create mode 100644 addon/doxysearch/doxysearch.pro.in create mode 100644 doc/doxyindexer.1 create mode 100644 doc/doxysearch.1 create mode 100644 doc/extsearch.doc create mode 100644 src/extsearch.js create mode 100644 src/extsearch_js.h create mode 100644 winbuild/doxyindexer.vcproj create mode 100644 winbuild/doxysearch.vcproj diff --git a/INSTALL b/INSTALL index ce02cda..42f3548 100644 --- a/INSTALL +++ b/INSTALL @@ -1,7 +1,7 @@ -DOXYGEN Version 1.8.2-20121118 +DOXYGEN Version 1.8.3 Please read the installation section of the manual (http://www.doxygen.org/install.html) for instructions. -------- -Dimitri van Heesch (18 November 2012) +Dimitri van Heesch (26 December 2012) diff --git a/Makefile.in b/Makefile.in index 67a2ba0..1cd4f16 100644 --- a/Makefile.in +++ b/Makefile.in @@ -4,6 +4,12 @@ DESTDIR = +doxywizard: + cd addon/doxywizard ; $(MAKE) + +doxysearch: + cd addon/doxysearch ; $(MAKE) + clean: FORCE cd examples ; $(MAKE) clean cd doc ; $(MAKE) clean @@ -11,9 +17,11 @@ clean: FORCE cd src ; $(MAKE) clean cd libmd5 ; $(MAKE) clean -cd addon/doxywizard ; $(MAKE) clean - cd addon/doxmlparser/src ; $(MAKE) clean - cd addon/doxmlparser/test ; $(MAKE) clean - cd addon/doxmlparser/examples/metrics ; $(MAKE) clean + -cd addon/doxysearch ; $(MAKE) clean + -cd addon/doxyapp ; $(MAKE) clean + -cd addon/doxmlparser/src ; $(MAKE) clean + -cd addon/doxmlparser/test ; $(MAKE) clean + -cd addon/doxmlparser/examples/metrics ; $(MAKE) clean -rm -f bin/doxy* -rm -f objects/*.o @@ -21,10 +29,11 @@ distclean: clean cd src ; $(MAKE) distclean cd libmd5 ; $(MAKE) distclean -cd addon/doxywizard ; $(MAKE) distclean - cd addon/doxmlparser/src ; $(MAKE) distclean - cd addon/doxmlparser/test ; $(MAKE) distclean - cd addon/doxmlparser/examples/metrics ; $(MAKE) distclean - cd addon/doxyapp ; $(MAKE) distclean + -cd addon/doxysearch ; $(MAKE) distclean + -cd addon/doxyapp ; $(MAKE) distclean + -cd addon/doxmlparser/src ; $(MAKE) distclean + -cd addon/doxmlparser/test ; $(MAKE) distclean + -cd addon/doxmlparser/examples/metrics ; $(MAKE) distclean -rm -f lib/lib* -rm -f bin/doxy* -rm -f html @@ -46,9 +55,9 @@ DATE=$(shell date "+%B %Y") MAN1DIR = man/man1 -install: doxywizard_install +install: doxywizard_install doxysearch_install $(INSTTOOL) -d $(DESTDIR)/$(INSTALL)/bin - $(INSTTOOL) -m 755 bin/doxygen $(DESTDIR)/$(INSTALL)/bin + $(INSTTOOL) -m 755 bin/doxygen $(DESTDIR)/$(INSTALL)/bin $(INSTTOOL) -d $(DESTDIR)/$(INSTALL)/$(MAN1DIR) cat doc/doxygen.1 | sed -e "s/DATE/$(DATE)/g" -e "s/VERSION/$(VERSION)/g" > doxygen.1 $(INSTTOOL) -m 644 doxygen.1 $(DESTDIR)/$(INSTALL)/$(MAN1DIR)/doxygen.1 diff --git a/README b/README index 59812fc..847c028 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -DOXYGEN Version 1.8.2_20121118 +DOXYGEN Version 1.8.3 Please read INSTALL for compilation instructions. @@ -26,4 +26,4 @@ forum. Enjoy, -Dimitri van Heesch (dimitri@stack.nl) (18 November 2012) +Dimitri van Heesch (dimitri@stack.nl) (26 December 2012) 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 +#include +#include +#include +#include +#include + +// Qtools includes +#include +#include +#include +#include + +// Xapian include +#include + +#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 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,">",">"); + replace_all(result,"<","<"); + replace_all(result,"'","'"); + replace_all(result,""","\""); + replace_all(result,"&","&"); + 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 and 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 and tags */ + bool endElement(const QString &, const QString &, const QString &name) + { + if (name=="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) // + { + // 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-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 +#include +#include +#include +#include +#include +#include +#include +#include + +// Xapian includes +#include + +#ifdef _WIN32 +#include +#else +#include +#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 split(const std::string &s, char delim) +{ + std::vector 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 +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' 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 &ranges,int start,int len) +{ + for (std::vector::const_iterator it = ranges.begin(); + it!=ranges.end(); ++it + ) + { + Range r = *it; + if (start>=r.start && start+len &words, + std::vector &fragments) +{ + const std::string spanStart=""; + const std::string spanEnd=""; + const std::string dots="..."; + const int fragLen = 60; + int sl=s.length(); + + // find positions of words in s + size_t j=0; + std::vector positions; + for (std::vector::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 ranges; + for (std::vector::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::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=2) + { + queryString = argv[1]; + } + else + { + std::cout << "No input!\n"; + exit(1); + } + + // parse query string + std::vector parts = split(queryString,'&'); + std::string searchFor,callback; + int num=1,page=0; + for (std::vector::const_iterator it=parts.begin();it!=parts.end();++it) + { + std::vector 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(val); + else if (kv[0]=="p") page = fromString(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 words = split(searchFor,' '); + for (std::vector::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 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::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> .makeconfig <> .tmakeconfig <> $DST if test $i = Makefile.in; then echo "" >> $DST - echo "all: src/version.cpp " >> $DST + EXTRADEPS= + if test $f_wizard = YES; then + EXTRADEPS=doxywizard + fi + if test $f_search = YES; then + EXTRADEPS="$EXTRADEPS doxysearch" + fi + echo "all: src/version.cpp $EXTRADEPS" >> $DST echo " \$(MAKE) -C qtools" >> $DST echo " \$(MAKE) -C libmd5" >> $DST echo " \$(MAKE) -C src" >> $DST if test $f_wizard = YES; then echo " \$(MAKE) MAN1DIR=\$(MAN1DIR) -C addon/doxywizard" >> $DST fi + if test $f_search = YES; then + echo " \$(MAKE) -C addon/doxysearch" >> $DST + fi if test $f_app = YES; then echo " \$(MAKE) -C addon/doxyapp" >> $DST fi @@ -622,6 +661,10 @@ EOF if test $f_wizard = YES; then echo " \$(MAKE) MAN1DIR=\$(MAN1DIR) -C addon/doxywizard install" >> $DST fi + echo "doxysearch_install:" >> $DST + if test $f_search = YES; then + echo " \$(MAKE) MAN1DIR=\$(MAN1DIR) -C addon/doxysearch install" >> $DST + fi echo "" >> $DST fi if test $f_wizard = YES; then @@ -635,7 +678,7 @@ done cat src/libdoxycfg.t.in | sed -e "s|%%FLEX%%|$f_flex|g" -e "s|%%BISON%%|$f_bison|g" > src/libdoxycfg.t cat src/libdoxygen.t.in | sed -e "s|%%FLEX%%|$f_flex|g" -e "s|%%BISON%%|$f_bison|g" > src/libdoxygen.t -f_inprofiles="qtools/qtools.pro.in src/libdoxygen.pro.in src/libdoxycfg.pro.in src/doxygen.pro.in addon/doxywizard/doxywizard.pro.in addon/doxmlparser/src/doxmlparser.pro.in addon/doxmlparser/test/xmlparse.pro.in addon/doxmlparser/examples/metrics/metrics.pro.in libmd5/libmd5.pro.in addon/doxyapp/doxyapp.pro.in" +f_inprofiles="qtools/qtools.pro.in src/libdoxygen.pro.in src/libdoxycfg.pro.in src/doxygen.pro.in addon/doxywizard/doxywizard.pro.in addon/doxmlparser/src/doxmlparser.pro.in addon/doxmlparser/test/xmlparse.pro.in addon/doxmlparser/examples/metrics/metrics.pro.in libmd5/libmd5.pro.in addon/doxyapp/doxyapp.pro.in addon/doxysearch/doxysearch.pro.in addon/doxysearch/doxyindexer.pro.in" for i in $f_inprofiles ; do SRC=$i diff --git a/doc/Doxyfile b/doc/Doxyfile index 1b0a311..50cede6 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -35,7 +35,7 @@ CASE_SENSE_NAMES = NO IMAGE_PATH = . INPUT = index.doc install.doc starting.doc docblocks.doc markdown.doc \ lists.doc grouping.doc formulas.doc diagrams.doc preprocessing.doc \ - autolink.doc output.doc searching.doc customize.doc custcmd.doc \ + autolink.doc output.doc searching.doc extsearch.doc customize.doc custcmd.doc \ external.doc faq.doc trouble.doc features.doc \ doxygen_usage.doc doxywizard_usage.doc \ config.doc commands.doc htmlcmds.doc xmlcmds.doc language.doc \ diff --git a/doc/commands.doc b/doc/commands.doc index b19e38f..03d6d3c 100644 --- a/doc/commands.doc +++ b/doc/commands.doc @@ -55,7 +55,6 @@ documentation: \refitem cmdclass \\class \refitem cmdcode \\code \refitem cmdcond \\cond -\refitem cmdcondnot \\condnot \refitem cmdcopybrief \\copybrief \refitem cmdcopydetails \\copydetails \refitem cmdcopydoc \\copydoc @@ -1083,8 +1082,10 @@ Section indicators The section between \\cond and \\endcond commands can be included by adding its section label to the \ref cfg_enabled_sections "ENABLED_SECTIONS" configuration option. If the section label is omitted, the section will - be excluded from processing unconditionally. The section label can be a logical expression - build of section names, round brackets, && (AND), || (OR) and ! (NOT). + be excluded from processing unconditionally. The section label can be a + logical expression build of section lavels, round brackets, && (AND), || (OR) and ! (NOT). + If you use an expression you need to wrap it in round brackets, i.e + \\cond (!LABEL1 && LABEL2). For conditional sections within a comment block one should use a \ref cmdif "\\if" ... \ref cmdendif "\\endif" block. @@ -1137,30 +1138,7 @@ class Implementation : public Intf The output will be different depending on whether or not \c ENABLED_SECTIONS contains \c TEST, or \c DEV - \sa section \ref cmdcondnot "\\condnot" and section \ref cmdendcond "\\endcond". - -
-\section cmdcondnot \\condnot [(section-label)] - - \addindex \\condnot - Starts a conditional section that ends with a corresponding - \ref cmdendcond "\\endcond" command, which is typically found in - another comment block. The main purpose of this pair of - commands is to (conditionally) exclude part of a file from processing - (in older version of doxygen this could only be achieved using C preprocessor commands). - - The section between \\condnot and \\endcond commands can be excluded by - adding its section label to the \ref cfg_enabled_sections "ENABLED_SECTIONS" - configuration option. The section label can be a logical expression - build of section names, round brackets, && (AND), || (OR) and ! (NOT). - - For conditional sections within a comment block one should - use a \ref cmdif "\\if" ... \ref cmdendif "\\endif" block. - - Conditional sections can be nested. In this case a nested section will only - be shown if it and its containing section are included. - - \sa section \ref cmdcond "\\cond" and section \ref cmdendcond "\\endcond". + \sa section \ref cmdendcond "\\endcond".
\section cmdcopyright \\copyright { copyright description } @@ -1233,9 +1211,9 @@ contains \c TEST, or \c DEV \section cmdendcond \\endcond \addindex \\endcond - Ends a conditional section that was started by \ref cmdcond "\\cond" or \ref cmdcondnot "\\condnot". + Ends a conditional section that was started by \ref cmdcond "\\cond". - \sa section \ref cmdcond "\\cond" and section \ref cmdcondnot "\\condnot". + \sa section \ref cmdcond "\\cond".
\section cmdendif \\endif @@ -1269,8 +1247,13 @@ contains \c TEST, or \c DEV with a matching \c \\endif command. A conditional section is disabled by default. To enable it you must put the section-label after the \ref cfg_enabled_sections "ENABLED_SECTIONS" - tag in the configuration file. The section label can be a logical expression + tag in the configuration file. + + The section label can be a logical expression build of section names, round brackets, && (AND), || (OR) and ! (NOT). + If you use an expression you need to wrap it in round brackets, i.e + \\cond (!LABEL1 && LABEL2). + Conditional blocks can be nested. A nested section is only enabled if all enclosing sections are enabled as well. diff --git a/doc/config.doc b/doc/config.doc index 64456c7..2fdabb0 100644 --- a/doc/config.doc +++ b/doc/config.doc @@ -114,7 +114,9 @@ followed by the descriptions of the tags grouped by category. \refitem cfg_ext_links_in_window EXT_LINKS_IN_WINDOW \refitem cfg_extension_mapping EXTENSION_MAPPING \refitem cfg_external_groups EXTERNAL_GROUPS +\refitem cfg_external_search EXTERNAL_SEARCH \refitem cfg_extra_packages EXTRA_PACKAGES +\refitem cfg_extra_search_mappings EXTRA_SEARCH_MAPPINGS \refitem cfg_extract_all EXTRACT_ALL \refitem cfg_extract_anon_nspaces EXTRACT_ANON_NSPACES \refitem cfg_extract_local_classes EXTRACT_LOCAL_CLASSES @@ -251,7 +253,9 @@ followed by the descriptions of the tags grouped by category. \refitem cfg_rtf_output RTF_OUTPUT \refitem cfg_rtf_stylesheet_file RTF_STYLESHEET_FILE \refitem cfg_search_includes SEARCH_INCLUDES +\refitem cfg_searchdata_file SEARCHDATA_FILE \refitem cfg_searchengine SEARCHENGINE +\refitem cfg_searchengine_url SEARCHENGINE_URL \refitem cfg_separate_member_pages SEPARATE_MEMBER_PAGES \refitem cfg_server_based_search SERVER_BASED_SEARCH \refitem cfg_short_names SHORT_NAMES @@ -503,7 +507,7 @@ followed by the descriptions of the tags grouped by category.
\c SIP_SUPPORT
\addindex OPTIMIZE_OUTPUT_SIP Set the SIP_SUPPORT tag to YES if your project consists - of sip sources only. + of sip sources only. Doxygen will parse them like normal C++ but will assume all classes use public instead of private inheritance when no explicit protection keyword is present. @@ -1762,43 +1766,6 @@ The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. the directory name containing the HTML and XML files should also have this name. Each documentation set should have its own identifier. -\anchor cfg_searchengine -
\c SEARCHENGINE
- \addindex SEARCHENGINE - - When the \c SEARCHENGINE tag is enabled doxygen will generate a search box - for the HTML output. The underlying search engine uses javascript - and DHTML and should work on any modern browser. Note that when using - HTML help (\ref cfg_generate_htmlhelp "GENERATE_HTMLHELP"), - Qt help (\ref cfg_generate_qhp "GENERATE_QHP"), or docsets - (\ref cfg_generate_docset "GENERATE_DOCSET") there is already a search - function so this one should typically be disabled. For large projects - the javascript based search engine can be slow, then enabling - \ref cfg_server_based_search "SERVER_BASED_SEARCH" may provide a - better solution. - - It is possible to search using the keyboard; - to jump to the search box use access key + S (what the access key is - depends on the OS and browser, but it is typically CTRL, ALT/option, or both). - Inside the search box use the cursor down key to jump into the search - results window, the results can be navigated using the cursor keys. - Press Enter to select an item or escape to cancel the search. The - filter options can be selected when the cursor is inside the search box - by pressing Shift+cursor down. Also here use the cursor keys to - select a filter and enter or escape to activate or cancel the filter option. - -\anchor cfg_server_based_search -
\c SERVER_BASED_SEARCH
- \addindex SERVER_BASED_SEARCH - -When the SERVER_BASED_SEARCH tag is enabled the search engine will be -implemented using a PHP enabled web server instead of at the web client -using Javascript. Doxygen will generate the search PHP script and index -file to put on the web server. The advantage of the server -based approach is that it scales better to large projects and also allows -full text search. The disadvantages are that it is more difficult to setup -and does not have live searching capabilities. - \anchor cfg_disable_index
\c DISABLE_INDEX
\addindex DISABLE_INDEX @@ -1907,6 +1874,91 @@ for more details. \verbatim MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols \endverbatim + +\anchor cfg_searchengine +
\c SEARCHENGINE
+ \addindex SEARCHENGINE + + When the \c SEARCHENGINE tag is enabled doxygen will generate a search box + for the HTML output. The underlying search engine uses javascript + and DHTML and should work on any modern browser. Note that when using + HTML help (\ref cfg_generate_htmlhelp "GENERATE_HTMLHELP"), + Qt help (\ref cfg_generate_qhp "GENERATE_QHP"), or docsets + (\ref cfg_generate_docset "GENERATE_DOCSET") there is already a search + function so this one should typically be disabled. For large projects + the javascript based search engine can be slow, then enabling + \ref cfg_server_based_search "SERVER_BASED_SEARCH" may provide a + better solution. + + It is possible to search using the keyboard; + to jump to the search box use access key + S (what the access key is + depends on the OS and browser, but it is typically CTRL, ALT/option, or both). + Inside the search box use the cursor down key to jump into the search + results window, the results can be navigated using the cursor keys. + Press Enter to select an item or escape to cancel the search. The + filter options can be selected when the cursor is inside the search box + by pressing Shift+cursor down. Also here use the cursor keys to + select a filter and enter or escape to activate or cancel the filter option. + +\anchor cfg_server_based_search +
\c SERVER_BASED_SEARCH
+ \addindex SERVER_BASED_SEARCH + +When the \c SERVER_BASED_SEARCH tag is enabled the search engine will be +implemented using a web server instead of a web client using Javascript. + +There are two flavours of web server based searching depending on the +\ref cfg_external_search "EXTERNAL_SEARCH" setting. When disabled, +doxygen will generate a PHP script for searching and an index file used +by the script. When \ref cfg_external_search "EXTERNAL_SEARCH" is +enabled the indexing and searching needs to be provided by external tools. +See \ref extsearch for details. + +\anchor cfg_external_search +
\c EXTERNAL_SEARCH
+ \addindex EXTERNAL_SEARCH + +When \c EXTERNAL_SEARCH is enabled doxygen will no longer generate the PHP +script for searching. Instead the search results are written to an XML file +which needs to be processed by an external indexer. Doxygen will invoke an +external search engine pointed to by the +\ref cfg_searchengine_url "SEARCHENGINE_URL" option to obtain +the search results. See the section \ref extsearch for details. + +\anchor cfg_searchengine_url +
\c SEARCHENGINE_URL
+ \addindex SEARCHENGINE_URL + +The \c SEARCHENGINE_URL should point to a search engine hosted by a web server +which will returned the search results when \ref cfg_external_search "EXTERNAL_SEARCH" +is enabled. See the section \ref extsearch for details. + +\anchor cfg_searchdata_file +
\c SEARCHDATA_FILE
+ \addindex SEARCHDATA_FILE + +When \ref cfg_server_based_search "SERVER_BASED_SEARCH" and +\ref cfg_external_search "EXTERNAL_SEARCH" are both enabled the unindexed +search data is written to a file for indexing by an external tool. With the +\c SEARCHDATA_FILE tag the name of this file can be specified. +The default is searchdata.xml. + +\anchor cfg_extra_search_mappings +
\c EXTRA_SEARCH_MAPPINGS
+ \addindex EXTRA_SEARCH_MAPPINGS + +The \c EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through other +doxygen projects that are not otherwise connected via tags files, but are +all added to the same search index. Each project needs to have a tag file set +via \ref cfg_generate_tagfile "GENERATE_TAGFILE". The search mapping then +maps the name of tag file to a relative location where the documentation +can be found, similar to the \ref cfg_tagfiles "TAGFILES" option but without +actually processing the tag file. The format is: + +\verbatim +EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +\endverbatim + \section latex_output LaTeX related options diff --git a/doc/doxygen_manual.css b/doc/doxygen_manual.css index 644e4d7..45395b4 100644 --- a/doc/doxygen_manual.css +++ b/doc/doxygen_manual.css @@ -871,7 +871,7 @@ div.header div.headertitle { - padding: 5px 5px 5px 7px; + padding: 5px 5px 5px 0px; } dl diff --git a/doc/doxygen_manual.tex b/doc/doxygen_manual.tex index 1c9ad2a..cd9a544 100644 --- a/doc/doxygen_manual.tex +++ b/doc/doxygen_manual.tex @@ -90,6 +90,7 @@ Written by Dimitri van Heesch\\[2ex] \chapter{Automatic link generation}\label{autolink}\hypertarget{autolink}{}\input{autolink} \chapter{Output Formats}\label{output}\hypertarget{output}{}\input{output} \chapter{Searching}\label{searching}\hypertarget{searching}{}\input{searching} +\chapter{External Indexing and Searching}\label{extsearch}\hypertarget{extsearch}{}\input{extsearch} \chapter{Customizing the Output}\label{customize}\hypertarget{customize}{}\input{customize} \chapter{Custom Commands}\label{custcmd}\hypertarget{custcmd}{}\input{custcmd} \chapter{Link to external documentation}\label{external}\hypertarget{external}{}\input{external} diff --git a/doc/doxyindexer.1 b/doc/doxyindexer.1 new file mode 100644 index 0000000..7b7a298 --- /dev/null +++ b/doc/doxyindexer.1 @@ -0,0 +1,17 @@ +.TH DOXYINDEXER "1" "DATE" "doxyindexer VERSION" "User Commands" +.SH NAME +doxyindexer \- creates a search index from raw search data +.SH SYNOPSIS +.B doxyindexer +[\fI-o output_dir\fR] \fIsearchdata.xml \fR[\fIsearchdata2.xml\fR...] ] +.SH DESCRIPTION +Generates a search index called \fBdoxysearch.db\fR from one or more +search data files produced by doxygen. Use +doxysearch.cgi as a CGI program to search the data indexed by doxyindexer. +.SH OPTIONS +.TP +\fB\-o\fR +The directory where to write the doxysearch.db to. +If omitted the current directory is used. +.SH SEE ALSO +doxygen(1), doxysearch(1), doxywizard(1). diff --git a/doc/doxysearch.1 b/doc/doxysearch.1 new file mode 100644 index 0000000..da9ae05 --- /dev/null +++ b/doc/doxysearch.1 @@ -0,0 +1,11 @@ +.TH DOXYSEARCH "1" "DATE" "doxysearch.cgi VERSION" "User Commands" +.SH NAME +doxysearch.cgi \- search engine used for searching in doxygen documentation. +.SH SYNOPSIS +.B doxyindexer.cgi +.SH DESCRIPTION +CGI binary that is used by doxygen generated HTML output to search for words. +The tool uses the search index called \fBdoxysearch.db\fR produced by +doxyindexer. +.SH SEE ALSO +doxygen(1), doxysearch(1), doxywizard(1). diff --git a/doc/extsearch.doc b/doc/extsearch.doc new file mode 100644 index 0000000..a86d1db --- /dev/null +++ b/doc/extsearch.doc @@ -0,0 +1,320 @@ +/****************************************************************************** + * + * 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. + * + */ +/*! \page extsearch External Indexing and Searching + +\section extsearch_intro Introduction + +With release 1.8.3, doxygen provides the ability to search through HTML using +an external indexing tool and search engine. +This has several advantages: +- For large projects it can have significant performance advantages over + doxygen's built-in search engine, as doxygen uses a rather simple indexing + algorithm. +- It allows combining the search data of multiple projects into one index, + allowing a global search across multiple doxygen projects. +- It allows adding additional data to the search index, i.e. other web pages + not produced by doxygen. +- The search engine needs to run on a web server, but clients can still browse + the web pages locally. + +To avoid that everyone has to start writing their own indexer and search +engine, doxygen provides an example tool for each action: `doxyindexer` +for indexing the data and `doxysearch.cgi` for searching through the index. + +The data flow is shown in the following diagram: +\dot +digraph Flow { + edge [fontname="helvetica",fontsize="10pt"]; + node [shape=ellipse,fontname="helvetica",fontsize="10pt"]; + doxygen; + doxyindexer; + doxysearch [label="doxysearch.cgi"]; + browser [label="HTML page\nin browser"]; + node [shape=note]; + searchdata [label="searchdata.xml"]; + searchindex [label="doxysearch.db"]; + + doxygen -> searchdata [label=" writes"]; + searchdata -> doxyindexer [label=" reads"]; + doxyindexer -> searchindex [label=" writes"]; + searchindex -> doxysearch [label=" reads"]; + doxysearch -> browser [label=" get results "]; + browser -> doxysearch [label=" query "]; +} +\enddot + +- `doxygen` produces the raw search data +- `doxyindexer` indexes the data into a search database `doxysearch.db` +- when a user performs a search from a doxygen generated HTML page, + the CGI binary `doxysearch.cgi` will be invoked. +- the `doxysearch.cgi` tool will perform a query on the database and return + the results. +- The browser will show the search results. + +\section extsearch_config Configuring + +The first step is to make the search engine available via a web server. +If you use `doxysearch.cgi` this means making the +CGI binary +available from the web server (i.e. be able to run it from a +browser via an URL starting with http:) + +How to setup a web server is outside the scope of this document, +but if you for instance have Apache installed, you could simply copy the +`doxysearch.cgi` file from doxygen's `bin` dir to the `cgi-bin` of the +Apache web server. Read the apache documentation for details. + +To test if `doxysearch.cgi` is accessible start your web browser and +point to URL to the binary and add `?test` at the end + + http://yoursite.com/path/to/cgi/doxysearch.cgi?test + +You should get the following message: + + Test failed: cannot find search index doxysearch.db + +If you use Internet Explorer you may be prompted to download a file, +which will then contain this message. + +Since we didn't create or install a doxysearch.db it is ok for the test to +fail for this reason. How to correct this is discussed in the next section. + +Before continuing with the next section add the above +URL (without the `?test` part) to the `SEARCHENGINE_URL` tag in +doxygen's configuration file: + + SEARCHENGINE_URL = http://yoursite.com/path/to/cgi/doxysearch.cgi + +\subsection extsearch_single Single project index + +To use the external search option, make sure the following options are enabled +in doxygen's configuration file: + + SEARCHENGINE = YES + SERVER_BASED_SEARCH = YES + EXTERNAL_SEARCH = YES + +This will make doxygen generate a file called `searchdata.xml` in the output +directory (configured with \ref cfg_output_directory "OUTPUT_DIRECTORY"). +You can change the file name (and location) with the +\ref cfg_searchdata_file "SEARCHDATA_FILE" option. + +The next step is to put the raw search data into an index for efficient +searching. You can use `doxyindexer` for this. Simply run it from the command +line: + + doxyindexer searchdata.xml + +This will create a directory called `doxysearch.db` with some files in it. +By default the directory will be created at the location from which doxyindexer +was started, but you can change the directory using the `-o` option. + +Copy the `doxysearch.db` directory to the same directory as where +the `doxysearch.cgi` is located and rerun the browser test by pointing +the browser to + + http://yoursite.com/path/to/cgi/doxysearch.cgi?test + +You should now get the following message: + + Test successful. + +Now you should be enable to search for words and symbols from the HTML output. + +\subsection extsearch_multi Multi project index + +In case you have two doxygen projects A and B where B depends on A via a +tag file, i.e. the configuration of project A says: + + GENERATE_TAGFILES = A.tag + +and the configuration of project B has its dependency on A configured as +follows: + + TAGFILES = ../project_A/A.tag=../../project_A/html + +then it may be desirable to allow searching for words in both projects. + +To make this possible all that is needed is to combine the search data +for both projects into one index, i.e. run + + doxyindexer project_A/searchdata.xml project_B/searchdata.xml + +and then copy the resulting `doxysearch.db` to the directory where also +`doxysearch.cgi` used by project B is located. + +In case you also want to link to search results in project B +from the search page of project A (or in general +between two projects that are otherwise unrelated), +you need to give some additional information in order for doxygen to make +the right links. This is what the +\ref cfg_extra_search_mappings "EXTRA_SEARCH_MAPPINGS" option is for. + +Each project needs to have a tag file defined, i.e. in the above example +involving project A and B, also project B should define a tag file: + + GENERATE_TAGFILES = B.tag + +then project A can define the mapping as follows: + + EXTRA_SEARCH_MAPPINGS = B.tag=../../project_B/html + +with this addition, projects A and B can share the same search database. + +@note The mapping defined by `EXTRA_SEARCH_MAPPINGS` is treated as an +extension of the mappings already defined by `TAGFILES`. In case the same +tag file is mentioned in both options, the one in `TAGFILES` is used. + +\section extsearch_update Updating the index + +When you modify the source code, you should re-run doxygen to get up to date +documentation again. When using external searching you also need to update the +search index by re-running `doxyindexer`. You could wrap the call to doxygen +and doxyindexer together in a script to make this process easier. + +\section extsearch_api Programming interface + +Previous sections have assumed you use the tools `doxyindexer` +and `doxysearch.cgi` to do the indexing and searching, but you could also +write your own index and search tools if you like. + +For this 3 interfaces are important +- The format of the input for the index tool. +- The format of the input for the search engine. +- The format of the output of search engine. + +The next subsections describe these interfaces in more detail. + +\subsection extsearch_api_index Indexer input format + +The search data produced by doxygen follows the +Solr XML index message +format. + +The input for the indexer is an XML file, which consists of one `` tag containing +multiple `` tags, which in turn contain multiple `` tags. + +Here is an example of one doc node, which contains the search data and meta data for +one method: + + + ... + + function + QXmlReader::setDTDHandler + (QXmlDTDHandler *handler)=0 + qtools.tag + de/df6/class_q_xml_reader.html#a0b24b1fe26a4c32a8032d68ee14d5dba + setDTDHandler QXmlReader::setDTDHandler QXmlReader + Sets the DTD handler to handler DTDHandler() + + ... + + +Each field has a name. The following field names are supported: +- *type*: the type of the search entry; can be one of: source, function, slot, + signal, variable, typedef, enum, enumvalue, property, event, related, + friend, define, file, namespace, group, package, page, dir +- *name*: the name of the search entry; for a method this is the qualified name of the method, + for a class it is the name of the class, etc. +- *args*: the parameter list (in case of functions or methods) +- *tag*: the name of the tag file used for this project. +- *url*: the (relative) URL to the HTML documentation for this entry. +- *keywords*: important words that are representative for the entry. When searching for such + keyword, this entry should get a higher rank in the search results. +- *text*: the documentation associated with the item. Note that only words are present, no markup. + +@note Due to the potentially large size of the XML file, it is recommended to use a +SAX based parser to process it. + +\subsection extsearch_api_search_in Search URL format + +When the search engine is invoked from a doxygen generated HTML page, a number of parameters are +passed to via the query string. + +The following fields are passed: +- *q*: the query text as entered by the user +- *n*: the number of search results requested. +- *p*: the number of search page for which to return the results. Each page has *n* values. +- *cb*: the name of the callback function, used for JSON with padding, see the next section. + +From the complete list of search results, the range `[n*p - n*(p+1)-1]` should be returned. + +Here is an example of how a query looks like. + + http://yoursite.com/path/to/cgi/doxysearch.cgi?q=list&n=20&p=1&cb=dummy + +It represents a query for the word 'list' (`q=list`) requesting 20 search results (`n=20`), +starting with the result number 20 (`p=1`) and using callback 'dummy' (`cb=dummy`): + + +@note The values are URL encoded so they +have to be decoded before they can be used. + +\subsection extsearch_api_search_out Search results format + +When invoking the search engine as shown in the previous subsection, it should reply with +the results. The format of the reply is +JSON with padding, which is basically +a javascript struct wrapped in a function call. The name of function should be the name of +the callback (as passed with the *cb* field in the query). + +With the example query as shown the previous subsection the main structure of the reply should +look as follows: + + dummy({ + "hits":179, + "first":20, + "count":20, + "page":1, + "pages":9, + "query": "list", + "items":[ + ... + ]}) + +The fields have the following meaning: +- *hits*: the total number of search results (could be more than was requested). +- *first*: the index of first result returned: \f$\min(n*p,\mbox{\em hits})\f$. +- *count*: the actual number of results returned: \f$\min(n,\mbox{\em hits}-\mbox{\em first})\f$ +- *page*: the page number of the result: \f$p\f$ +- *pages*: the total number of pages: \f$\lceil\frac{\mbox{\em hits}}{n}\rceil\f$. +- *items*: an array containing the search data per result. + +Here is an example of how the element of the *items* array should look like: + + {"type": "function", + "name": "QDir::entryInfoList(const QString &nameFilter, int filterSpec=DefaultFilter, int sortSpec=DefaultSort) const", + "tag": "qtools.tag", + "url": "d5/d8d/class_q_dir.html#a9439ea6b331957f38dbad981c4d050ef", + "fragments":[ + "Returns a list of QFileInfo objects for all files and directories...", + "... pointer to a QFileInfoList The list is owned by the QDir object...", + "... to keep the entries of the list after a subsequent call to this..." + ] + }, + +The fields for such an item have the following meaning: +- *type*: the type of the item, as found in the field with name "type" in the raw search data. +- *name*: the name of the item, including the parameter list, as found in the fields with + name "name" and "args" in the raw search data. +- *tag*: the name of the tag file, as found in the field with name "tag" in the raw search data. +- *url*: the name of the (relative) URL to the documentation, as found in the field with name "url" + in the raw search data. +- "fragments": an array with 0 or more fragments of text containing words that have been search for. + These words should be wrapped in `` and `` tags to highlight them + in the output. + +*/ diff --git a/doc/faq.doc b/doc/faq.doc index cc8a9a4..0475aa7 100644 --- a/doc/faq.doc +++ b/doc/faq.doc @@ -80,7 +80,7 @@ document either the class or namespace.
  • How can I make doxygen ignore some code fragment? The new and easiest way is to add one comment block -with a \ref cmdcond "\\cond" or \ref cmdcondnot "\\condnot" command at the start and one comment block +with a \ref cmdcond "\\cond" command at the start and one comment block with a \ref cmdendcond "\\endcond" command at the end of the piece of code that should be ignored. This should be within the same file of course. diff --git a/doc/index.doc b/doc/index.doc index 5207b37..3b2947d 100644 --- a/doc/index.doc +++ b/doc/index.doc @@ -76,6 +76,7 @@ The first part forms a user manual:
  • Section \ref output shows how to generate the various output formats supported by doxygen.
  • Section \ref searching shows various ways to search in the HTML documentation. +
  • Section \ref extsearch shows how use the external search and index tools
  • Section \ref customize explains how you can customize the output generated by doxygen.
  • Section \ref custcmd show how to define and use custom commands in your comments. diff --git a/doc/install.doc b/doc/install.doc index d8a75a2..4622065 100644 --- a/doc/install.doc +++ b/doc/install.doc @@ -338,24 +338,18 @@ compile doxygen. Alternatively, you can compile doxygen Cygwin or MinGW. -The next step is to install unxutils (see http://sourceforge.net/projects/unxutils). -This packages contains the tools \c flex and \c bison which are needed during the +The next step is to install bison, flex, and tar +(see http://gnuwin32.sourceforge.net/packages.html). +This packages are needed during the compilation process if you use a CVS snapshot of doxygen (the official source releases come with pre-generated sources). -Download the zip extract it to e.g. c:\\tools\\unxutils. - -Now you need to add/adjust the following environment variables -(via Control Panel/System/Advanced/Environment Variables): -- add c:\\tools\\unxutils\\usr\\local\\wbin; to the start of PATH -- set BISON_SIMPLE to c:\\tools\\unxutils\\usr\\local\\share\\bison.simple Download doxygen's source tarball and put it somewhere (e.g. use c:\\tools) Now start a new command shell and type \verbatim cd c:\tools -gunzip doxygen-x.y.z.src.tar.gz -tar xvf doxygen-x.y.z.src.tar +tar zxvf doxygen-x.y.z.src.tar.gz \endverbatim to unpack the sources. diff --git a/doc/language.doc b/doc/language.doc index b1cdf4e..b0e2ffc 100644 --- a/doc/language.doc +++ b/doc/language.doc @@ -23,7 +23,7 @@ text fragments, generated by doxygen, can be produced in languages other than English (the default). The output language is chosen through the configuration file (with default name and known as Doxyfile). -Currently (version 1.8.1.2), 39 languages +Currently (version 1.8.2), 39 languages are supported (sorted alphabetically): Afrikaans, Arabic, Armenian, Brazilian Portuguese, Catalan, Chinese, Chinese Traditional, Croatian, Czech, Danish, Dutch, English, diff --git a/doc/searching.doc b/doc/searching.doc index b3e4fd9..f80e9f6 100644 --- a/doc/searching.doc +++ b/doc/searching.doc @@ -25,7 +25,7 @@ HTML browsers by default have no search capabilities that work across multiple pages, so either doxygen or external tools need to help to facilitate this feature. -Doxygen has 6 different ways to add searching to the HTML output, each of which +Doxygen has 7 different ways to add searching to the HTML output, each of which has its own advantages and disadvantages:

    1. Client side searching

    @@ -54,15 +54,45 @@ has its own advantages and disadvantages: To enable this set both \ref cfg_searchengine "SEARCHENGINE" and \ref cfg_server_based_search "SERVER_BASED_SEARCH" to \c YES in the config - file. + file and set \ref cfg_external_search "EXTERNAL_SEARCH" to \c NO. Advantages over the client side search engine are that it provides full - text search and it scales well to large projects. + text search and it scales well to medium side projects. Disadvantages are that it does not work locally (i.e. using a file:// URL) and that it does not provide live search capabilities. -

    3. Windows Compiled HTML Help

    + @note In the future this option will probably be replaced by the next + search option. + +

    3. Server side searching with external indexing

    + With release 1.8.3 of doxygen, another server based search option has + been added. With this option doxygen generates the raw data that can be + searched and leaves it up to external tools to do the indexing and + searching, meaning that you could use your own indexer and search engine + of choice. To make life easier doxygen ships with an example indexer + (doxyindexer) and search engine (doxysearch.cgi) based on + the Xapian open source search engine + library. + + To enable this search method set + \ref cfg_searchengine "SEARCHENGINE", + \ref cfg_server_based_search "SERVER_BASED_SEARCH" and + \ref cfg_external_search "EXTERNAL_SEARCH" all to \c YES. + + See \ref extsearch for configuration details. + + Advantages over option 2 are that this method (potentially) scales to + very large projects. It is also possible to combine multiple doxygen + projects and external data into one search index. + The way the interaction with the search engine is done, makes it + possible to search from local HTML pages. Also the search results have + better ranking and show context information (if available). + + Disadvantages are that is requires a web server that can execute a CGI + binary, and an additional indexing step after running doxygen. + +

    4. Windows Compiled HTML Help

    If you are running doxygen on Windows, then you can make a compiled HTML Help file (.chm) out of the HTML files produced by doxygen. This is a single file containing all HTML files and it also includes a @@ -83,7 +113,7 @@ has its own advantages and disadvantages: by Microsoft. Although the tool works fine for most people, it can sometimes crash for no apparent reason (how typical). -

    4. Mac OS X Doc Sets

    +

    5. Mac OS X Doc Sets

    If you are running doxygen on Mac OS X 10.5 or higher, then you can make a "doc set" out of the HTML files produced by doxygen. A doc set consists of a single directory with a special structure @@ -106,7 +136,7 @@ has its own advantages and disadvantages: Disadvantage is that it only works in combination with Xcode on MacOSX. -

    5. Qt Compressed Help

    +

    6. Qt Compressed Help

    If you develop for or want to install the Qt application framework, you will get an application called Qt assistant. @@ -129,7 +159,7 @@ has its own advantages and disadvantages: the documentation, which is complicated by the fact that it is not available as a separate package at this moment. -

    6. Eclipse Help Plugin

    +

    7. Eclipse Help Plugin

    If you use eclipse, you can embed the documentation generated by doxygen as a help plugin. It will then appear as a topic in the help browser that can be started from "Help contents" in the Help menu. diff --git a/doc/translator_report.txt b/doc/translator_report.txt index b364de4..ad25228 100644 --- a/doc/translator_report.txt +++ b/doc/translator_report.txt @@ -1,4 +1,4 @@ -(1.8.1.2) +(1.8.2) Doxygen supports the following 39 languages (sorted alphabetically): diff --git a/packages/rpm/doxygen.spec.in b/packages/rpm/doxygen.spec.in index 2ae6067..2888ef2 100644 --- a/packages/rpm/doxygen.spec.in +++ b/packages/rpm/doxygen.spec.in @@ -47,9 +47,24 @@ This is the GUI interface for doxygen. It requires qt and X11 to install. %endif +%if %{?_with_doxysearch:1}%{!?_with_doxysearch:0} +%package doxysearch +Group: Development/Libraries +Summary: external indexer and search engine for doxygen. +Requires: doxygen = %{mmn} +Requires: libxapian-devel >= 1.2 +Provides: doxysearch.cgi = %{mmn} +Provides: doxyindexer = %{mmn} +# Obsoletes: + +%description doxysearch +External indexing and search tools for searching through doxygen +generated HTML documentation. +%endif + %prep %setup -q -n %{name}-%{version} -./configure %{?_with_doxywizard} --prefix $RPM_BUILD_ROOT/usr +./configure %{?_with_doxywizard} %{?_with_doxysearch} --prefix $RPM_BUILD_ROOT/usr %build make %{?_smp_mflags} @@ -77,7 +92,19 @@ rm -rf $RPM_BUILD_ROOT %doc /usr/man/man1/doxywizard.1.gz %endif +%if %{?_with_doxysearch:1}%{!?_with_doxysearch:0} +%files doxysearch +%defattr(-,root,root) +%{_bindir}/doxyindexer +%{_bindir}/doxysearch.cgi +%doc /usr/man/man1/doxyindexer.1.gz +%doc /usr/man/man1/doxysearch.1.gz +%endif + %changelog +* Tue Dec 25 2012 Dimitri van Heesch 1.8.3 +- added doxyindexer and doxysearch + * Fri Apr 18 2008 Kenneth Porter 1.5.5-1 - consolidate with and without doxywizard spec files with rpm macro - add gs BuildPrereq diff --git a/qtools/Doxyfile b/qtools/Doxyfile index 23d4e31..8aa5e83 100644 --- a/qtools/Doxyfile +++ b/qtools/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.0 +# Doxyfile 1.8.3 #--------------------------------------------------------------------------- # Project related configuration options @@ -34,6 +34,7 @@ OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO SIP_SUPPORT = NO @@ -114,10 +115,11 @@ INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- -SOURCE_BROWSER = NO +SOURCE_BROWSER = YES INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = YES @@ -140,12 +142,14 @@ HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 220 HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 HTML_TIMESTAMP = YES HTML_DYNAMIC_SECTIONS = YES +HTML_INDEX_NUM_ENTRIES = 100 GENERATE_DOCSET = YES DOCSET_FEEDNAME = "Doxygen generated docs" DOCSET_BUNDLE_ID = org.doxygen.Project @@ -176,10 +180,15 @@ EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest MATHJAX_EXTENSIONS = SEARCHENGINE = YES SERVER_BASED_SEARCH = YES +EXTERNAL_SEARCH = YES +SEARCHENGINE_URL = http://macbookpro/~dimitri/doxysearch.cgi +SEARCHDATA_FILE = searchdata.xml +EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- diff --git a/src/classdef.cpp b/src/classdef.cpp index 4349d3f..8517893 100644 --- a/src/classdef.cpp +++ b/src/classdef.cpp @@ -2220,7 +2220,7 @@ void ClassDef::writeMemberList(OutputList &ol) { //ol.writeListItem(); ol.writeString(" "); if (cd->isObjectiveC()) @@ -4106,15 +4106,14 @@ void ClassDef::writeInheritedMemberDeclarations(OutputList &ol, int lt1,lt2; convertProtectionLevel(lt,ibcd->prot,<1,<2); //printf("%s:convert %d->(%d,%d)\n",icd->name().data(),lt,lt1,lt2); - if (visitedClasses->find(icd)!=0) - { - return; // already processed before (in case of multiple inheritance) - } - visitedClasses->insert(icd,icd); - if (lt1!=-1) + if (visitedClasses->find(icd)==0) { - icd->writeMemberDeclarations(ol,(MemberListType)lt1, - title,QCString(),FALSE,inheritedFrom,lt2,invert,visitedClasses); + visitedClasses->insert(icd,icd); // guard for multiple virtual inheritance + if (lt1!=-1) + { + icd->writeMemberDeclarations(ol,(MemberListType)lt1, + title,QCString(),FALSE,inheritedFrom,lt2,invert,visitedClasses); + } } } } diff --git a/src/code.l b/src/code.l index 337f400..8e11d59 100644 --- a/src/code.l +++ b/src/code.l @@ -427,11 +427,11 @@ static void setCurrentDoc(const QCString &anchor) { if (g_searchCtx) { - Doxygen::searchIndex->setCurrentDoc(g_searchCtx,g_searchCtx->anchor(),FALSE); + g_code->setCurrentDoc(g_searchCtx,g_searchCtx->anchor(),FALSE); } else { - Doxygen::searchIndex->setCurrentDoc(g_sourceFileDef,anchor,TRUE); + g_code->setCurrentDoc(g_sourceFileDef,anchor,TRUE); } } } @@ -440,7 +440,7 @@ static void addToSearchIndex(const char *text) { if (Doxygen::searchIndex) { - Doxygen::searchIndex->addWord(text,FALSE); + g_code->addWord(text,FALSE); } } diff --git a/src/commentcnv.l b/src/commentcnv.l index 88d9ce3..9d99830 100644 --- a/src/commentcnv.l +++ b/src/commentcnv.l @@ -183,16 +183,16 @@ static void startCondSection(const char *sectId) CondParser prs; bool expResult = prs.parse(g_fileName,g_lineNr,sectId); g_condStack.push(new CondCtx(g_lineNr,sectId,g_skip)); - if (guardType == Guard_Cond) + if (guardType == Guard_Cond) // found @cond { - if (expResult) + if (!expResult) // not enabled { g_skip=TRUE; } } - else if (guardType == Guard_CondNot) + else if (guardType == Guard_CondNot) // found @notcond { - if (!expResult) + if (expResult) // enabled { g_skip=TRUE; } @@ -294,7 +294,7 @@ void replaceComment(int offset); \n { /* new line */ copyToOutput(yytext,(int)yyleng); } -("//!"|"///").*/\n[ \t]*"//"[\/!][^\/] { /* start C++ style special comment block */ +("//!"|"///")/.*\n[ \t]*"//"[\/!][^\/] { /* start C++ style special comment block */ if (g_mlBrief) { REJECT; // bail out if we do not need to convert @@ -310,7 +310,9 @@ void replaceComment(int offset); copyToOutput("/**",3); replaceAliases(yytext+i); g_inSpecialComment=TRUE; - BEGIN(SComment); + //BEGIN(SComment); + g_readLineCtx=SComment; + BEGIN(ReadLine); } } "//##Documentation".*/\n { /* Start of Rational Rose ANSI C++ comment block */ @@ -323,6 +325,7 @@ void replaceComment(int offset); BEGIN(SComment); } "//"/.*\n { /* one line C++ comment */ + g_inSpecialComment=yytext[2]=='/' || yytext[2]=='!'; copyToOutput(yytext,(int)yyleng); g_readLineCtx=YY_START; BEGIN(ReadLine); @@ -679,11 +682,6 @@ void replaceComment(int offset); guardType = Guard_Cond; BEGIN(CondLine); } -[\\@]"condnot"[ \t]+ { // conditional section - g_condCtx = YY_START; - guardType = Guard_CondNot; - BEGIN(CondLine); - } [\\@]"endcond"/[^a-z_A-Z0-9] { // end of conditional section bool oldSkip=g_skip; endCondSection(); @@ -706,9 +704,9 @@ void replaceComment(int offset); [!()&| \ta-z_A-Z0-9.\-]+ { bool oldSkip=g_skip; startCondSection(yytext); - if (g_condCtx==CComment && !oldSkip && g_skip) + if ((g_condCtx==CComment || g_readLineCtx==SComment) && + !oldSkip && g_skip) { - //printf("** Adding terminator for comment!\n"); if (g_lang!=SrcLangExt_Python && g_lang!=SrcLangExt_VHDL && g_lang!=SrcLangExt_Fortran) @@ -717,7 +715,14 @@ void replaceComment(int offset); ADDCHAR('/'); } } - BEGIN(g_condCtx); + if (g_readLineCtx==SComment) + { + BEGIN(SComment); + } + else + { + BEGIN(g_condCtx); + } } [ \t]* [\\@]"cond"[ \t\r]*/\n | @@ -726,7 +731,8 @@ void replaceComment(int offset); if (YY_START!=CondLine) g_condCtx=YY_START; bool oldSkip=g_skip; startCondSection(" "); // fake section id causing the section to be hidden unconditionally - if (g_condCtx==CComment && !oldSkip && g_skip) + if ((g_condCtx==CComment || g_readLineCtx==SComment) && + !oldSkip && g_skip) { //printf("** Adding terminator for comment!\n"); if (g_lang!=SrcLangExt_Python && @@ -737,7 +743,14 @@ void replaceComment(int offset); } } if (*yytext=='\n') g_lineNr++; - BEGIN(g_condCtx); + if (g_readLineCtx==SComment) + { + BEGIN(SComment); + } + else + { + BEGIN(g_condCtx); + } } [\\@][a-z_A-Z][a-z_A-Z0-9]* { // expand alias without arguments replaceAliases(yytext); @@ -804,7 +817,7 @@ void replaceComment(int offset); void replaceComment(int offset) { - if (g_mlBrief) + if (g_mlBrief || g_skip) { copyToOutput(yytext,(int)yyleng); } diff --git a/src/commentscan.l b/src/commentscan.l index 375b0f9..68f6b5f 100644 --- a/src/commentscan.l +++ b/src/commentscan.l @@ -413,6 +413,9 @@ static bool g_spaceBeforeCmd; static bool g_spaceBeforeIf; static QCString g_copyDocArg; +static QCString g_guardExpr; +static int g_roundCount; + //----------------------------------------------------------------------------- static QStack g_autoGroupStack; @@ -807,6 +810,7 @@ static void endBrief(bool addToOutput=TRUE) } } +static void handleGuard(const QCString &expr); /* ----------------------------------------------------------------- */ #undef YY_INPUT #define YY_INPUT(buf,result,max_size) result=yyread(buf,max_size); @@ -917,6 +921,7 @@ RCSTAG "$"{ID}":"[^\n$]+"$" %x SkipLang %x CiteLabel %x CopyDoc +%x GuardExpr %% @@ -1032,7 +1037,7 @@ RCSTAG "$"{ID}":"[^\n$]+"$" } int i=0; while (yytext[i]==' ' || yytext[i]=='\t') i++; - if (i>0) addOutput(QCString(yytext).left(i)); + //if (i>0) addOutput(QCString(yytext).left(i)); // removed for bug 689341 if (cmdPtr->func && cmdPtr->func(cmdName)) { // implicit split of the comment block into two @@ -1725,36 +1730,34 @@ RCSTAG "$"{ID}":"[^\n$]+"$" /* ----- handle arguments of if/ifnot commands ------- */ -[!()&| \ta-z_A-Z0-9.\-]+ {// parameter of if/ifnot guard - CondParser prs; - bool sectionEnabled=prs.parse(yyFileName,yyLineNr,yytext); - bool parentEnabled = TRUE; - if (!guards.isEmpty()) parentEnabled = guards.top()->isEnabled(); - if (parentEnabled) - { - if ( - (sectionEnabled && guardType==Guard_If) || - (!sectionEnabled && guardType==Guard_IfNot) - ) // section is visible - { - guards.push(new GuardedSection(TRUE,TRUE)); - enabledSectionFound=TRUE; - BEGIN( GuardParamEnd ); - } - else // section is invisible - { - if (guardType!=Guard_Skip) - { - guards.push(new GuardedSection(FALSE,TRUE)); - } - BEGIN( SkipGuardedSection ); - } - } - else // invisible because of parent - { - guards.push(new GuardedSection(FALSE,FALSE)); - BEGIN( SkipGuardedSection ); - } +"(" { + g_guardExpr=yytext; + g_roundCount=1; + BEGIN(GuardExpr); + } +[^()]* { + g_guardExpr+=yytext; + } +"(" { + g_guardExpr+=yytext; + g_roundCount++; + } +")" { + g_guardExpr+=yytext; + g_roundCount--; + if (g_roundCount==0) + { + handleGuard(g_guardExpr); + } + } +\n { + warn(yyFileName,yyLineNr, + "warning: invalid expression '%s' for guard",g_guardExpr.data()); + unput(*yytext); + BEGIN(GuardParam); + } +[a-z_A-Z0-9.\-]+ { // parameter of if/ifnot guard + handleGuard(yytext); } {DOCNL} { // end of argument if (*yytext=='\n') yyLineNr++; @@ -2952,6 +2955,40 @@ static void groupAddDocs(Entry *e,const char *fileName) } } +static void handleGuard(const QCString &expr) +{ + CondParser prs; + bool sectionEnabled=prs.parse(yyFileName,yyLineNr,expr); + bool parentEnabled = TRUE; + if (!guards.isEmpty()) parentEnabled = guards.top()->isEnabled(); + if (parentEnabled) + { + if ( + (sectionEnabled && guardType==Guard_If) || + (!sectionEnabled && guardType==Guard_IfNot) + ) // section is visible + { + guards.push(new GuardedSection(TRUE,TRUE)); + enabledSectionFound=TRUE; + BEGIN( GuardParamEnd ); + } + else // section is invisible + { + if (guardType!=Guard_Skip) + { + guards.push(new GuardedSection(FALSE,TRUE)); + } + BEGIN( SkipGuardedSection ); + } + } + else // invisible because of parent + { + guards.push(new GuardedSection(FALSE,FALSE)); + BEGIN( SkipGuardedSection ); + } +} + + #if !defined(YY_FLEX_SUBMINOR_VERSION) //---------------------------------------------------------------------------- diff --git a/src/condparser.cpp b/src/condparser.cpp index 4920ade..2cadc1e 100644 --- a/src/condparser.cpp +++ b/src/condparser.cpp @@ -37,18 +37,19 @@ bool CondParser::parse(const char *fileName,int lineNr,const char *expr) m_tokenType = NOTHING; // initialize all variables - m_e = m_expr.data(); // let m_e point to the start of the expression + m_e = m_expr; // let m_e point to the start of the expression + bool answer=FALSE; getToken(); if (m_tokenType==DELIMITER && m_token.isEmpty()) { - return "Empty expression"; + // empty expression: answer==FALSE } - bool answer=FALSE; - if (m_err.isEmpty()) + else if (m_err.isEmpty()) { answer = parseLevel1(); +#if 0 // check for garbage at the end of the expression // an expression ends with a character '\0' and token_type = delimeter if (m_tokenType!=DELIMITER || !m_token.isEmpty()) @@ -70,12 +71,14 @@ bool CondParser::parse(const char *fileName,int lineNr,const char *expr) m_err=QCString("Unexpected part '")+m_token+"'"; } } +#endif } if (m_err) { warn(fileName,lineNr,"Warning: problem evaluating expression '%s': %s", expr,m_err.data()); } + //printf("expr='%s' answer=%d\n",expr,answer); return answer; } diff --git a/src/config.l b/src/config.l index 7c37dea..0422411 100644 --- a/src/config.l +++ b/src/config.l @@ -506,26 +506,9 @@ static FILE *tryPath(const char *path,const char *fileName) static void substEnvVarsInStrList(QStrList &sl); static void substEnvVarsInString(QCString &s); -static bool isAbsolute(const char * fileName) -{ -# ifdef _WIN32 - if (isalpha (fileName [0]) && fileName[1] == ':') - fileName += 2; -# endif - char const fst = fileName [0]; - if (fst == '/') { - return true; - } -# ifdef _WIN32 - if (fst == '\\') - return true; -# endif - return false; -} - static FILE *findFile(const char *fileName) { - if(isAbsolute(fileName)) + if(portable_isAbsolutePath(fileName)) return tryPath(NULL, fileName); substEnvVarsInStrList(includePathList); char *s=includePathList.first(); @@ -1458,7 +1441,7 @@ void Config::check() config_err("error: Unsupported value for MATHJAX_FORMAT: Should be one of HTML-CSS, NativeMML, or SVG\n"); Config_getEnum("MATHJAX_FORMAT")="HTML-CSS"; } - + // add default words if needed QStrList &annotationFromBrief = Config_getList("ABBREVIATE_BRIEF"); if (annotationFromBrief.isEmpty()) diff --git a/src/config.xml b/src/config.xml index 3ca3313..a75ff80 100644 --- a/src/config.xml +++ b/src/config.xml @@ -1148,12 +1148,42 @@ can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. ' defval='1' depends='GENERATE_HTML'/>
  • \n"; + t << " \n"; + t << " \n"; + t << "\n"; + } + else + { + t << "" << endl; + } + t << writeSplitBarAsString("search",""); + t << "
    " << endl; + t << "
    " << endl; + t << "
    " << theTranslator->trSearchResultsTitle() << "
    " << endl; + t << "
    " << endl; + t << "
    " << endl; + t << "
    " << endl; + + t << "
    " << endl; + t << "
    " << endl; + + if (generateTreeView) + { + t << "" << endl; + } + writePageFooter(t,"Search","",""); + } + QCString scriptName = Config_getString("HTML_OUTPUT")+"/search/search.js"; + QFile sf(scriptName); + if (sf.open(IO_WriteOnly)) + { + FTextStream t(&sf); + t << "var searchResultsText=[" + << "\"" << theTranslator->trSearchResults(0) << "\"," + << "\"" << theTranslator->trSearchResults(1) << "\"," + << "\"" << theTranslator->trSearchResults(2) << "\"];" << endl; + t << "var serverUrl=\"" << Config_getString("SEARCHENGINE_URL") << "\";" << endl; + t << "var tagMap = {" << endl; + // add standard tag file mappings + QDictIterator it(Doxygen::tagDestinationDict); + QCString *dest; + bool first=TRUE; + for (it.toFirst();(dest=it.current());++it) + { + if (!first) t << "," << endl; + t << " \"" << it.currentKey() << "\": \"" << *dest << "\""; + first=FALSE; + } + // add additional user specified mappings + QStrList &extraSearchMappings = Config_getList("EXTRA_SEARCH_MAPPINGS"); + char *ml=extraSearchMappings.first(); + while (ml) + { + QCString mapLine = ml; + int eqPos = mapLine.find('='); + if (eqPos!=-1) // tag command contains a destination + { + QCString tagName = mapLine.left(eqPos).stripWhiteSpace(); + QCString destName = mapLine.right(mapLine.length()-eqPos-1).stripWhiteSpace(); + if (!tagName.isEmpty() && Doxygen::tagDestinationDict.find(tagName)==0) + { + if (!first) t << "," << endl; + t << " \"" << tagName << "\": \"" << destName << "\""; + first=FALSE; + } + } + ml=extraSearchMappings.next(); + } + if (!first) t << endl; + t << "};" << endl << endl; + t << extsearch_script; + t << endl; + t << "$(document).ready(function() {" << endl; + t << " var query = trim(getURLParameter('query'));" << endl; + t << " if (query) {" << endl; + t << " searchFor(query,0,20);" << endl; + t << " } else {" << endl; + t << " var results = $('#results');" << endl; + t << " results.html('

    " << theTranslator->trSearchResults(0) << "

    ');" << endl; + t << " }" << endl; + t << "});" << endl; + } + else + { + err("Failed to open file '%s' for writing...\n",scriptName.data()); + } } void HtmlGenerator::startConstraintList(const char *header) @@ -3284,3 +3382,19 @@ void HtmlGenerator::endMemberDeclaration(const char *anchor,const char *inheritI t << "\"> \n"; } +void HtmlGenerator::setCurrentDoc(Definition *context,const char *anchor,bool isSourceFile) +{ + if (Doxygen::searchIndex) + { + Doxygen::searchIndex->setCurrentDoc(context,anchor,isSourceFile); + } +} + +void HtmlGenerator::addWord(const char *word,bool hiPriority) +{ + if (Doxygen::searchIndex) + { + Doxygen::searchIndex->addWord(word,hiPriority); + } +} + diff --git a/src/htmlgen.h b/src/htmlgen.h index 78a4d0c..17f12fb 100644 --- a/src/htmlgen.h +++ b/src/htmlgen.h @@ -48,6 +48,8 @@ class HtmlCodeGenerator : public CodeOutputInterface void endFontClass(); void writeCodeAnchor(const char *anchor); void linkableSymbol(int,const char *,Definition *,Definition *); + void setCurrentDoc(Definition *,const char *,bool) {} + void addWord(const char *,bool) {} private: void docify(const char *str); @@ -71,6 +73,7 @@ class HtmlGenerator : public OutputGenerator static void writeSearchInfo(FTextStream &t,const QCString &relPath); static void writeSearchData(const char *dir); static void writeSearchPage(); + static void writeExternalSearchPage(); static QCString writeLogoAsString(const char *path); static QCString writeSplitBarAsString(const char *name,const char *relpath); @@ -111,6 +114,8 @@ class HtmlGenerator : public OutputGenerator { m_codeGen.linkableSymbol(line,symName,symDef,context); } // --------------------------- + void setCurrentDoc(Definition *context,const char *anchor,bool isSourceFile); + void addWord(const char *word,bool hiPriority); void writeDoc(DocNode *,Definition *,MemberDef *); void startFile(const char *name,const char *manName,const char *title); diff --git a/src/htmlhelp.cpp b/src/htmlhelp.cpp index 2fccdd6..945a7c5 100644 --- a/src/htmlhelp.cpp +++ b/src/htmlhelp.cpp @@ -460,7 +460,6 @@ void HtmlHelp::createProjectFile() FTextStream t(&f); QCString indexName="index"+Doxygen::htmlFileExtension; - //if (Config_getBool("GENERATE_TREEVIEW")) indexName="main"+Doxygen::htmlFileExtension; t << "[OPTIONS]\n"; if (!Config_getString("CHM_FILE").isEmpty()) { @@ -496,52 +495,6 @@ void HtmlHelp::createProjectFile() t << s << endl; s = indexFiles.next(); } -#if 0 - // items not found by the html help compiler scan. - t << "tabs.css" << endl; - t << "tab_a.png" << endl; - t << "tab_b.png" << endl; - t << "tab_h.png" << endl; - t << "tab_s.png" << endl; - t << "nav_h.png" << endl; - t << "nav_f.png" << endl; - t << "bc_s.png" << endl; - if (Config_getBool("HTML_DYNAMIC_SECTIONS")) - { - t << "open.png" << endl; - t << "closed.png" << endl; - } - if (Config_getBool("GENERATE_HTMLHELP")) - { - t << "ftv2blank.png" << endl; - t << "ftv2doc.png" << endl; - t << "ftv2folderclosed.png" << endl; - t << "ftv2folderopen.png" << endl; - t << "ftv2lastnode.png" << endl; - t << "ftv2link.png" << endl; - t << "ftv2mlastnode.png" << endl; - t << "ftv2mnode.png" << endl; - t << "ftv2node.png" << endl; - t << "ftv2plastnode.png" << endl; - t << "ftv2pnode.png" << endl; - t << "ftv2vertline.png" << endl; - } - if (Config_getBool("SEARCHENGINE")) - { - t << "search_l.png" << endl; - t << "search_m.png" << endl; - t << "search_r.png" << endl; - if (Config_getBool("SERVER_BASED_SEARCH")) - { - t << "mag.png" << endl; - } - else - { - t << "mag_sel.png" << endl; - t << "close.png" << endl; - } - } -#endif uint i; for (i=0;isearch_css.h +extsearch_js.h: extsearch.js + cat extsearch.js | $(TO_C_CMD) >extsearch_js.h + doxygen_css.h: doxygen.css cat doxygen.css | $(TO_C_CMD) >doxygen_css.h diff --git a/src/mangen.h b/src/mangen.h index a4af56c..f40d0c3 100644 --- a/src/mangen.h +++ b/src/mangen.h @@ -257,6 +257,8 @@ class ManGenerator : public OutputGenerator void writeCodeAnchor(const char *) {} void linkableSymbol(int,const char *,Definition *,Definition *) {} + void setCurrentDoc(Definition *,const char *,bool) {} + void addWord(const char *,bool) {} private: bool firstCol; diff --git a/src/markdown.cpp b/src/markdown.cpp index 2571a1a..15b36a3 100644 --- a/src/markdown.cpp +++ b/src/markdown.cpp @@ -299,6 +299,10 @@ static int findEmphasisChar(const char *data, int size, char c, int c_size) i++; } } + else if (i0 && data[j-1]=='>') indent=j+1; j++; } - if (j>0 && data[j-1]=='>') // disqualify last > if not followed by space + if (j>0 && data[j-1]=='>' && + !(j==size || data[j]=='\n')) // disqualify last > if not followed by space { indent--; j--; diff --git a/src/memberdef.cpp b/src/memberdef.cpp index d658c4a..1482180 100644 --- a/src/memberdef.cpp +++ b/src/memberdef.cpp @@ -2350,6 +2350,10 @@ void MemberDef::writeDocumentation(MemberList *ml,OutputList &ol, if (title.at(0)=='@') { ldef = title = "anonymous enum"; + if (!m_impl->enumBaseType.isEmpty()) + { + ldef+=" : "+m_impl->enumBaseType; + } } else { @@ -2897,6 +2901,10 @@ void MemberDef::writeMemberDocSimple(OutputList &ol, Definition *container) ol.startInlineMemberName(); ol.docify(doxyName); + if (isVariable() && argsString() && !isObjCMethod()) + { + linkifyText(TextGeneratorOLImpl(ol),getOuterScope(),getBodyDef(),this,argsString()); + } if (!m_impl->bitfields.isEmpty()) // add bitfields { linkifyText(TextGeneratorOLImpl(ol),getOuterScope(),getBodyDef(),this,m_impl->bitfields.simplifyWhiteSpace()); @@ -3545,13 +3553,13 @@ void MemberDef::writeEnumDeclaration(OutputList &typeDecl, typeDecl.endBold(); } typeDecl.writeChar(' '); - if (!m_impl->enumBaseType.isEmpty()) - { - typeDecl.writeChar(':'); - typeDecl.writeChar(' '); - typeDecl.docify(m_impl->enumBaseType); - typeDecl.writeChar(' '); - } + } + if (!m_impl->enumBaseType.isEmpty()) + { + typeDecl.writeChar(':'); + typeDecl.writeChar(' '); + typeDecl.docify(m_impl->enumBaseType); + typeDecl.writeChar(' '); } uint enumValuesPerLine = (uint)Config_getInt("ENUM_VALUES_PER_LINE"); @@ -4987,6 +4995,14 @@ void MemberDef::_addToSearchIndex() if (ln!=qn) { Doxygen::searchIndex->addWord(qn,TRUE); + if (getClassDef()) + { + Doxygen::searchIndex->addWord(getClassDef()->displayName(),TRUE); + } + else if (getNamespaceDef()) + { + Doxygen::searchIndex->addWord(getNamespaceDef()->displayName(),TRUE); + } } } } diff --git a/src/navtree.js b/src/navtree.js index d4b241a..3d8134a 100644 --- a/src/navtree.js +++ b/src/navtree.js @@ -75,24 +75,22 @@ function getScript(scriptName,func,show) function createIndent(o,domNode,node,level) { - if (node.parentNode && node.parentNode.parentNode) { - createIndent(o,domNode,node.parentNode,level+1); - } + var level=-1; + var n = node; + while (n.parentNode) { level++; n=n.parentNode; } var imgNode = document.createElement("img"); - imgNode.width = 16; + imgNode.style.paddingLeft=(16*level).toString()+'px'; + imgNode.width = 16; imgNode.height = 22; - if (level==0 && node.childrenData) { + imgNode.border = 0; + if (node.childrenData) { node.plus_img = imgNode; node.expandToggle = document.createElement("a"); node.expandToggle.href = "javascript:void(0)"; node.expandToggle.onclick = function() { if (node.expanded) { $(node.getChildrenUL()).slideUp("fast"); - if (node.isLast) { - node.plus_img.src = node.relpath+"ftv2plastnode.png"; - } else { - node.plus_img.src = node.relpath+"ftv2pnode.png"; - } + node.plus_img.src = node.relpath+"ftv2pnode.png"; node.expanded = false; } else { expandNode(o, node, false, false); @@ -100,33 +98,11 @@ function createIndent(o,domNode,node,level) } node.expandToggle.appendChild(imgNode); domNode.appendChild(node.expandToggle); + imgNode.src = node.relpath+"ftv2pnode.png"; } else { + imgNode.src = node.relpath+"ftv2node.png"; domNode.appendChild(imgNode); - } - if (level==0) { - if (node.isLast) { - if (node.childrenData) { - imgNode.src = node.relpath+"ftv2plastnode.png"; - } else { - imgNode.src = node.relpath+"ftv2lastnode.png"; - domNode.appendChild(imgNode); - } - } else { - if (node.childrenData) { - imgNode.src = node.relpath+"ftv2pnode.png"; - } else { - imgNode.src = node.relpath+"ftv2node.png"; - domNode.appendChild(imgNode); - } - } - } else { - if (node.isLast) { - imgNode.src = node.relpath+"ftv2blank.png"; - } else { - imgNode.src = node.relpath+"ftv2vertline.png"; - } - } - imgNode.border = "0"; + } } var animationInProgress = false; @@ -364,7 +340,7 @@ function showNode(o, node, index, hash) },true); } else { var rootBase = stripPath(o.toroot.replace(/\..+$/, '')); - if (rootBase=="index" || rootBase=="pages") { + if (rootBase=="index" || rootBase=="pages" || rootBase=="search") { expandNode(o, n, true, true); } selectAndHighlight(hash,n); @@ -420,6 +396,7 @@ function navTo(o,root,hash,relpath) var url=root+hash; var i=-1; while (NAVTREEINDEX[i+1]<=url) i++; + if (i==-1) { i=0; root=NAVTREE[0][1]; } // fallback: show index if (navTreeSubIndices[i]) { gotoNode(o,i,root,hash,relpath) } else { diff --git a/src/navtree_js.h b/src/navtree_js.h index b0c6d7c..2e4df6a 100644 --- a/src/navtree_js.h +++ b/src/navtree_js.h @@ -75,24 +75,22 @@ "\n" "function createIndent(o,domNode,node,level)\n" "{\n" -" if (node.parentNode && node.parentNode.parentNode) {\n" -" createIndent(o,domNode,node.parentNode,level+1);\n" -" }\n" +" var level=-1;\n" +" var n = node;\n" +" while (n.parentNode) { level++; n=n.parentNode; }\n" " var imgNode = document.createElement(\"img\");\n" -" imgNode.width = 16;\n" +" imgNode.style.paddingLeft=(16*level).toString()+'px';\n" +" imgNode.width = 16;\n" " imgNode.height = 22;\n" -" if (level==0 && node.childrenData) {\n" +" imgNode.border = 0;\n" +" if (node.childrenData) {\n" " node.plus_img = imgNode;\n" " node.expandToggle = document.createElement(\"a\");\n" " node.expandToggle.href = \"javascript:void(0)\";\n" " node.expandToggle.onclick = function() {\n" " if (node.expanded) {\n" " $(node.getChildrenUL()).slideUp(\"fast\");\n" -" if (node.isLast) {\n" -" node.plus_img.src = node.relpath+\"ftv2plastnode.png\";\n" -" } else {\n" -" node.plus_img.src = node.relpath+\"ftv2pnode.png\";\n" -" }\n" +" node.plus_img.src = node.relpath+\"ftv2pnode.png\";\n" " node.expanded = false;\n" " } else {\n" " expandNode(o, node, false, false);\n" @@ -100,33 +98,11 @@ " }\n" " node.expandToggle.appendChild(imgNode);\n" " domNode.appendChild(node.expandToggle);\n" +" imgNode.src = node.relpath+\"ftv2pnode.png\";\n" " } else {\n" +" imgNode.src = node.relpath+\"ftv2node.png\";\n" " domNode.appendChild(imgNode);\n" -" }\n" -" if (level==0) {\n" -" if (node.isLast) {\n" -" if (node.childrenData) {\n" -" imgNode.src = node.relpath+\"ftv2plastnode.png\";\n" -" } else {\n" -" imgNode.src = node.relpath+\"ftv2lastnode.png\";\n" -" domNode.appendChild(imgNode);\n" -" }\n" -" } else {\n" -" if (node.childrenData) {\n" -" imgNode.src = node.relpath+\"ftv2pnode.png\";\n" -" } else {\n" -" imgNode.src = node.relpath+\"ftv2node.png\";\n" -" domNode.appendChild(imgNode);\n" -" }\n" -" }\n" -" } else {\n" -" if (node.isLast) {\n" -" imgNode.src = node.relpath+\"ftv2blank.png\";\n" -" } else {\n" -" imgNode.src = node.relpath+\"ftv2vertline.png\";\n" -" }\n" -" }\n" -" imgNode.border = \"0\";\n" +" } \n" "}\n" "\n" "var animationInProgress = false;\n" @@ -364,7 +340,7 @@ " },true);\n" " } else {\n" " var rootBase = stripPath(o.toroot.replace(/\\..+$/, ''));\n" -" if (rootBase==\"index\" || rootBase==\"pages\") {\n" +" if (rootBase==\"index\" || rootBase==\"pages\" || rootBase==\"search\") {\n" " expandNode(o, n, true, true);\n" " }\n" " selectAndHighlight(hash,n);\n" @@ -420,6 +396,7 @@ " var url=root+hash;\n" " var i=-1;\n" " while (NAVTREEINDEX[i+1]<=url) i++;\n" +" if (i==-1) { i=0; root=NAVTREE[0][1]; } // fallback: show index\n" " if (navTreeSubIndices[i]) {\n" " gotoNode(o,i,root,hash,relpath)\n" " } else {\n" diff --git a/src/outputgen.h b/src/outputgen.h index df4c4e2..8d67e3d 100644 --- a/src/outputgen.h +++ b/src/outputgen.h @@ -73,6 +73,8 @@ class CodeOutputInterface virtual void writeCodeAnchor(const char *name) = 0; virtual void linkableSymbol(int line,const char *symName, Definition *symDef,Definition *context) = 0; + virtual void setCurrentDoc(Definition *context,const char *anchor,bool isSourceFile) = 0; + virtual void addWord(const char *word,bool hiPriority) = 0; }; /** Base Interface used for generating output outside of the diff --git a/src/outputlist.cpp b/src/outputlist.cpp index adab93b..5ea2a21 100644 --- a/src/outputlist.cpp +++ b/src/outputlist.cpp @@ -321,6 +321,7 @@ FORALL3(const char *a1,const char *a2,bool a3,a1,a2,a3) FORALL3(const char *a1,int a2,const char *a3,a1,a2,a3) FORALL3(const char *a1,const char *a2,SectionInfo::SectionType a3,a1,a2,a3) FORALL3(uchar a1,uchar a2,uchar a3,a1,a2,a3) +FORALL3(Definition *a1,const char *a2,bool a3,a1,a2,a3) FORALL4(SectionTypes a1,const char *a2,const char *a3,const char *a4,a1,a2,a3,a4) FORALL4(const char *a1,const char *a2,const char *a3,const char *a4,a1,a2,a3,a4) FORALL4(const char *a1,const char *a2,const char *a3,int a4,a1,a2,a3,a4) diff --git a/src/outputlist.h b/src/outputlist.h index 90a3723..23221d3 100644 --- a/src/outputlist.h +++ b/src/outputlist.h @@ -470,6 +470,11 @@ class OutputList : public OutputDocInterface { forall(&OutputGenerator::endFontClass); } void writeCodeAnchor(const char *name) { forall(&OutputGenerator::writeCodeAnchor,name); } + void setCurrentDoc(Definition *context,const char *anchor,bool isSourceFile) + { forall(&OutputGenerator::setCurrentDoc,context,anchor,isSourceFile); } + void addWord(const char *word,bool hiPriority) + { forall(&OutputGenerator::addWord,word,hiPriority); } + void startPlainFile(const char *name) { OutputGenerator *og=outputs->first(); @@ -529,6 +534,7 @@ class OutputList : public OutputDocInterface FORALLPROTO3(uchar,uchar,uchar); FORALLPROTO3(const char *,const char *,const char *); FORALLPROTO3(const ClassDiagram &,const char *,const char *); + FORALLPROTO3(Definition*,const char *,bool); FORALLPROTO4(SectionTypes,const char *,const char *,const char *); FORALLPROTO4(const char *,const char *,const char *,const char *); FORALLPROTO4(const char *,const char *,const char *,bool); diff --git a/src/portable.cpp b/src/portable.cpp index bb13ddb..554bb0b 100644 --- a/src/portable.cpp +++ b/src/portable.cpp @@ -409,3 +409,22 @@ void portable_sleep(int ms) usleep(1000*ms); #endif } + +bool portable_isAbsolutePath(const char *fileName) +{ +# ifdef _WIN32 + if (isalpha (fileName [0]) && fileName[1] == ':') + fileName += 2; +# endif + char const fst = fileName [0]; + if (fst == '/') { + return true; + } +# ifdef _WIN32 + if (fst == '\\') + return true; +# endif + return false; +} + + diff --git a/src/portable.h b/src/portable.h index de798dd..1471ce1 100644 --- a/src/portable.h +++ b/src/portable.h @@ -34,6 +34,7 @@ void portable_sysTimerStart(); void portable_sysTimerStop(); double portable_getSysElapsedTime(); void portable_sleep(int ms); +bool portable_isAbsolutePath(const char *fileName); extern "C" { void * portable_iconv_open(const char* tocode, const char* fromcode); @@ -42,6 +43,5 @@ extern "C" { int portable_iconv_close (void *cd); } - #endif diff --git a/src/pre.l b/src/pre.l index da9c8f3..fdaee6f 100644 --- a/src/pre.l +++ b/src/pre.l @@ -761,7 +761,7 @@ static inline void addTillEndOfString(const QCString &expr,QCString *rest, uint &pos,char term,QCString &arg) { int cc; - while ((cc=getNextChar(expr,rest,pos))!=EOF) + while ((cc=getNextChar(expr,rest,pos))!=EOF && cc!=0) { if (cc=='\\') arg+=(char)cc,cc=getNextChar(expr,rest,pos); else if (cc==term) return; @@ -804,7 +804,7 @@ static bool replaceFunctionMacro(const QCString &expr,QCString *rest,int pos,int // PHASE 1: read the macro arguments if (def->nargs==0) { - while ((cc=getNextChar(expr,rest,j))!=EOF) + while ((cc=getNextChar(expr,rest,j))!=EOF && cc!=0) { char c = (char)cc; if (c==')') break; @@ -813,7 +813,7 @@ static bool replaceFunctionMacro(const QCString &expr,QCString *rest,int pos,int else { while (!done && (argCountnargs || def->varArgs) && - ((cc=getNextChar(expr,rest,j))!=EOF) + ((cc=getNextChar(expr,rest,j))!=EOF && cc!=0) ) { char c=(char)cc; @@ -822,7 +822,7 @@ static bool replaceFunctionMacro(const QCString &expr,QCString *rest,int pos,int int level=1; arg+=c; //char term='\0'; - while ((cc=getNextChar(expr,rest,j))!=EOF) + while ((cc=getNextChar(expr,rest,j))!=EOF && cc!=0) { char c=(char)cc; //printf("processing %c: term=%c (%d)\n",c,term,term); @@ -871,14 +871,14 @@ static bool replaceFunctionMacro(const QCString &expr,QCString *rest,int pos,int { arg+=c; bool found=FALSE; - while (!found && (cc=getNextChar(expr,rest,j))!=EOF) + while (!found && (cc=getNextChar(expr,rest,j))!=EOF && cc!=0) { found = cc=='"'; if (cc=='\\') { c=(char)cc; arg+=c; - if ((cc=getNextChar(expr,rest,j))==EOF) break; + if ((cc=getNextChar(expr,rest,j))==EOF || cc==0) break; } c=(char)cc; arg+=c; @@ -888,14 +888,14 @@ static bool replaceFunctionMacro(const QCString &expr,QCString *rest,int pos,int { arg+=c; bool found=FALSE; - while (!found && (cc=getNextChar(expr,rest,j))!=EOF) + while (!found && (cc=getNextChar(expr,rest,j))!=EOF && cc!=0) { found = cc=='\''; if (cc=='\\') { c=(char)cc; arg+=c; - if ((cc=getNextChar(expr,rest,j))==EOF) break; + if ((cc=getNextChar(expr,rest,j))==EOF || cc==0) break; } c=(char)cc; arg+=c; @@ -2498,12 +2498,6 @@ CHARLIT (("'"\\[0-7]{1,3}"'")|("'"\\."'")|("'"[^'\\\n]{1,4}"'")) outputArray(yytext,yyleng); BEGIN(CondLine); } -[\\@]"condnot"[ \t]+ { // conditional section - guardType = Guard_CondNot; - g_condCtx = YY_START; - outputArray(yytext,(int)yyleng); - BEGIN(CondLine); - } [!()&| \ta-z_A-Z0-9.\-]+ { startCondSection(yytext); outputArray(yytext,(int)yyleng); @@ -2558,6 +2552,7 @@ CHARLIT (("'"\\[0-7]{1,3}"'")|("'"\\."'")|("'"[^'\\\n]{1,4}"'")) } \n { g_yyLineNr++; + outputChar('\n'); g_defLitText+=yytext; g_defText+=' '; } @@ -2809,7 +2804,7 @@ static int getNextChar(const QCString &expr,QCString *rest,uint &pos) else { int cc=yyinput(); - //printf("%c=yyinput()\n",cc); + //printf("%d=yyinput() %d\n",cc,EOF); return cc; } } diff --git a/src/rtfgen.h b/src/rtfgen.h index 92b858f..784cfcc 100644 --- a/src/rtfgen.h +++ b/src/rtfgen.h @@ -258,6 +258,8 @@ class RTFGenerator : public OutputGenerator void writeCodeAnchor(const char *) {} void linkableSymbol(int,const char *,Definition *,Definition *) {} + void setCurrentDoc(Definition *,const char *,bool) {} + void addWord(const char *,bool) {} static bool preProcessFileInplace(const char *path,const char *name); diff --git a/src/scanner.l b/src/scanner.l index c711765..8efca45 100644 --- a/src/scanner.l +++ b/src/scanner.l @@ -3053,13 +3053,24 @@ RAWEND ")"[^ \t\(\)\\]{0,16}\" ":" { - if (current->type.isEmpty()) // anonymous padding field, e.g. "int :7;" + if (current->type.isEmpty() && + current->name=="enum") // see bug 69041, C++11 style anon enum: 'enum : unsigned int {...}' { - addType(current); - current->name.sprintf("__pad%d__",padCount++); + current->section=Entry::ENUM_SEC; + current->name.resize(0); + current->args.resize(0); + BEGIN(EnumBaseType); } - BEGIN(BitFields); - current->bitfields+=":"; + else + { + if (current->type.isEmpty()) // anonymous padding field, e.g. "int :7;" + { + addType(current); + current->name.sprintf("__pad%d__",padCount++); + } + BEGIN(BitFields); + current->bitfields+=":"; + } } . { current->bitfields+=*yytext; @@ -3069,6 +3080,7 @@ RAWEND ")"[^ \t\(\)\\]{0,16}\" } \n { lineCount(); + current->args+=' '; } [;,] { QCString oldType = current->type; @@ -3428,7 +3440,7 @@ RAWEND ")"[^ \t\(\)\\]{0,16}\" current->program += yytext; } } -"{" { current->program += yytext ; +"{" { current->program += yytext ; ++curlyCount ; } "}" { @@ -4672,11 +4684,11 @@ RAWEND ")"[^ \t\(\)\\]{0,16}\" } } } +{ID}{BN}*"{" { // C++11 style initializer (see bug 688647) + lineCount(); + ++curlyCount; + } "{" { // C++11 style initializer - //addToBody(yytext); - //lastCurlyContext = FindMembers; - //curlyCount=0; - //BEGIN( SkipCurly ) ; unput('{'); BEGIN( Function ); } diff --git a/src/search.css b/src/search.css index 5e6b12f..e467ae0 100644 --- a/src/search.css +++ b/src/search.css @@ -236,3 +236,36 @@ DIV.searchresults { margin-left: 10px; margin-right: 10px; } + +/*---------------- External search page results */ + +.searchresult { + background-color: ##F2; +} + +.pages b { + color: white; + padding: 5px 5px 3px 5px; + background-image: url("../tab_a.png"); + background-repeat: repeat-x; + text-shadow: 0 1px 1px #000000; +} + +.pages { + line-height: 17px; + margin-left: 4px; + text-decoration: none; +} + +.hl { + font-weight: bold; +} + +#searchresults { + margin-bottom: 20px; +} + +.searchpages { + margin-top: 10px; +} + diff --git a/src/search_css.h b/src/search_css.h index 9b6656b..9c3b08a 100644 --- a/src/search_css.h +++ b/src/search_css.h @@ -236,3 +236,36 @@ " margin-left: 10px;\n" " margin-right: 10px;\n" "}\n" +"\n" +"/*---------------- External search page results */\n" +"\n" +".searchresult {\n" +" background-color: ##F2;\n" +"}\n" +"\n" +".pages b {\n" +" color: white;\n" +" padding: 5px 5px 3px 5px;\n" +" background-image: url(\"../tab_a.png\");\n" +" background-repeat: repeat-x;\n" +" text-shadow: 0 1px 1px #000000;\n" +"}\n" +"\n" +".pages {\n" +" line-height: 17px;\n" +" margin-left: 4px;\n" +" text-decoration: none;\n" +"}\n" +"\n" +".hl {\n" +" font-weight: bold;\n" +"}\n" +"\n" +"#searchresults {\n" +" margin-bottom: 20px;\n" +"}\n" +"\n" +".searchpages {\n" +" margin-top: 10px;\n" +"}\n" +"\n" diff --git a/src/search_functions.php b/src/search_functions.php index f5c3688..acd7f3c 100644 --- a/src/search_functions.php +++ b/src/search_functions.php @@ -17,14 +17,6 @@ function end_form($value) function end_page() { - //global $config; - //global $translator; - //if ($config['GENERATE_TREEVIEW']) - //{ - // echo "\n
    \n
      \n
    • "; - // echo $translator['logo']; - // echo "
    • \n
    \n
    "; - //} echo ""; } diff --git a/src/search_functions_php.h b/src/search_functions_php.h index 1b72ae7..b09c259 100644 --- a/src/search_functions_php.h +++ b/src/search_functions_php.h @@ -17,14 +17,6 @@ "\n" "function end_page()\n" "{\n" -" //global $config;\n" -" //global $translator;\n" -" //if ($config['GENERATE_TREEVIEW'])\n" -" //{\n" -" // echo \"\\n
    \\n
      \\n
    • \";\n" -" // echo $translator['logo'];\n" -" // echo \"
    • \\n
    \\n
    \";\n" -" //}\n" " echo \"\";\n" "}\n" "\n" diff --git a/src/searchindex.cpp b/src/searchindex.cpp index 42c9114..fef0f13 100644 --- a/src/searchindex.cpp +++ b/src/searchindex.cpp @@ -192,9 +192,9 @@ static int charsToIndex(const char *word) //return h; // Simple hashing that allows for substring searching - uint c1=word[0]; + uint c1=((uchar *)word)[0]; if (c1==0) return -1; - uint c2=word[1]; + uint c2=((uchar *)word)[1]; if (c2==0) return -1; return c1*256+c2; } @@ -202,16 +202,16 @@ static int charsToIndex(const char *word) void SearchIndex::addWord(const char *word,bool hiPriority,bool recurse) { static QRegExp nextPart("[_a-z:][A-Z]"); - //printf("SearchIndex::addWord(%s,%d)\n",word,hiPriority); if (word==0 || word[0]=='\0') return; QCString wStr = QCString(word).lower(); + //printf("SearchIndex::addWord(%s,%d) wStr=%s\n",word,hiPriority,wStr.data()); IndexWord *w = m_words[wStr]; if (w==0) { int idx=charsToIndex(wStr); + //fprintf(stderr,"addWord(%s) at index %d\n",word,idx); if (idx<0) return; w = new IndexWord(wStr); - //fprintf(stderr,"addWord(%s) at index %d\n",word,idx); m_index[idx]->append(w); m_words.insert(wStr,w); } @@ -413,6 +413,7 @@ struct SearchDocEntry { QCString type; QCString name; + QCString args; QCString tagFile; QCString url; GrowBuf importantText; @@ -530,10 +531,15 @@ void SearchIndexExternal::setCurrentDoc(Definition *ctx,const char *anchor,bool SearchDocEntry *e = new SearchDocEntry; e->type = isSourceFile ? QCString("source") : definitionToName(ctx); e->name = ctx->qualifiedName(); + if (ctx->definitionType()==Definition::TypeMember) + { + e->args = ((MemberDef*)ctx)->argsString(); + } e->tagFile = tagFile; e->url = url; p->current = e; p->docEntries.append(key,e); + //printf("searchIndexExt %s : %s\n",e->name.data(),e->url.data()); } } @@ -543,6 +549,7 @@ void SearchIndexExternal::addWord(const char *word,bool hiPriority) GrowBuf *pText = hiPriority ? &p->current->importantText : &p->current->normalText; if (pText->getPos()>0) pText->addChar(' '); pText->addStr(word); + //printf("addWord %s\n",word); } void SearchIndexExternal::write(const char *fileName) @@ -562,7 +569,14 @@ void SearchIndexExternal::write(const char *fileName) t << " " << endl; t << " " << doc->type << "" << endl; t << " " << convertToXML(doc->name) << "" << endl; - t << " " << convertToXML(doc->tagFile) << "" << endl; + if (!doc->args.isEmpty()) + { + t << " " << convertToXML(doc->args) << "" << endl; + } + if (!doc->tagFile.isEmpty()) + { + t << " " << convertToXML(doc->tagFile) << "" << endl; + } t << " " << convertToXML(doc->url) << "" << endl; t << " " << convertToXML(doc->importantText.get()) << "" << endl; t << " " << convertToXML(doc->normalText.get()) << "" << endl; @@ -1356,12 +1370,19 @@ void writeSearchCategories(FTextStream &t) void initSearchIndexer() { - static bool searchEngine = Config_getBool("SEARCHENGINE"); + static bool searchEngine = Config_getBool("SEARCHENGINE"); static bool serverBasedSearch = Config_getBool("SERVER_BASED_SEARCH"); + static bool externalSearch = Config_getBool("EXTERNAL_SEARCH"); if (searchEngine && serverBasedSearch) { - //Doxygen::searchIndex = new SearchIndexExternal; - Doxygen::searchIndex = new SearchIndex; + if (externalSearch) // external tools produce search index and engine + { + Doxygen::searchIndex = new SearchIndexExternal; + } + else // doxygen produces search index and engine + { + Doxygen::searchIndex = new SearchIndex; + } } else // no search engine or pure javascript based search function { diff --git a/src/store.cpp b/src/store.cpp index 74719a9..521aa9b 100644 --- a/src/store.cpp +++ b/src/store.cpp @@ -402,21 +402,23 @@ void Store::dumpBlock(portable_off_t s,portable_off_t e) portable_fseek(m_file,s,SEEK_SET); int size = (int)(e-s); uchar *buf = new uchar[size]; - (void)fread(buf,size,1,m_file); - int i,j; - for (i=0;i=32 && buf[i+j]<128)?buf[i+j]:'.'); + printf("%08x: ",(int)s+i); + for (j=i;j=32 && buf[i+j]<128)?buf[i+j]:'.'); + } + printf("\n"); } - printf("\n"); } delete[] buf; portable_fseek(m_file,m_cur,SEEK_SET); diff --git a/src/tagreader.cpp b/src/tagreader.cpp index 553d195..42dcf68 100644 --- a/src/tagreader.cpp +++ b/src/tagreader.cpp @@ -44,9 +44,13 @@ class TagAnchorInfo { public: - TagAnchorInfo(const QCString &f,const QCString &l) : label(l), fileName(f) {} + TagAnchorInfo(const QCString &f, + const QCString &l, + const QCString &t=QCString()) + : label(l), fileName(f), title(t) {} QCString label; QCString fileName; + QCString title; }; /** List of TagAnchorInfo objects. */ @@ -417,7 +421,7 @@ class TagFileParser : public QXmlDefaultHandler case InFile: m_curFile->docAnchors.append(new TagAnchorInfo(m_fileName,m_curString)); break; case InNamespace: m_curNamespace->docAnchors.append(new TagAnchorInfo(m_fileName,m_curString)); break; case InGroup: m_curGroup->docAnchors.append(new TagAnchorInfo(m_fileName,m_curString)); break; - case InPage: m_curPage->docAnchors.append(new TagAnchorInfo(m_fileName,m_curString)); break; + case InPage: m_curPage->docAnchors.append(new TagAnchorInfo(m_fileName,m_curString,m_title)); break; case InMember: m_curMember->docAnchors.append(new TagAnchorInfo(m_fileName,m_curString)); break; case InPackage: m_curPackage->docAnchors.append(new TagAnchorInfo(m_fileName,m_curString)); break; case InDir: m_curDir->docAnchors.append(new TagAnchorInfo(m_fileName,m_curString)); break; @@ -485,6 +489,7 @@ class TagFileParser : public QXmlDefaultHandler void startDocAnchor(const QXmlAttributes& attrib ) { m_fileName = attrib.value("file").utf8(); + m_title = attrib.value("title").utf8(); m_curString = ""; } @@ -825,6 +830,7 @@ class TagFileParser : public QXmlDefaultHandler QCString m_curString; QCString m_tagName; QCString m_fileName; + QCString m_title; State m_state; QStack m_stateStack; QXmlLocator *m_locator; @@ -1041,7 +1047,7 @@ void TagFileParser::addDocAnchors(Entry *e,const TagAnchorInfoList &l) { //printf("New sectionInfo file=%s anchor=%s\n", // ta->fileName.data(),ta->label.data()); - SectionInfo *si=new SectionInfo(ta->fileName,ta->label,ta->label, + SectionInfo *si=new SectionInfo(ta->fileName,ta->label,ta->title, SectionInfo::Anchor,0,m_tagName); Doxygen::sectionDict->append(ta->label,si); e->anchors->append(si); diff --git a/src/tclscanner.l b/src/tclscanner.l index aaeac41..c3d5df0 100644 --- a/src/tclscanner.l +++ b/src/tclscanner.l @@ -1946,7 +1946,7 @@ static void tcl_command_NAMESPACE() { D QCString myNs, myName, myStr; - Entry *myEntryNs=NULL; + //Entry *myEntryNs=NULL; tcl_scan *myScan = tcl.scan.at(0); tcl_codify_cmd("keyword",0); @@ -1967,7 +1967,7 @@ D tcl.entry_current->endBodyLine = tcl.line_body1; tcl.entry_main->addSubEntry(tcl.entry_current); tcl.ns.insert(myName,tcl.entry_current); - myEntryNs = tcl.entry_current; + //myEntryNs = tcl.entry_current; myStr = (*tcl.list_commandwords.at(6)).utf8(); if (tcl.list_commandwords.count() > 7) { diff --git a/src/translator_cn.h b/src/translator_cn.h index 94a40dc..197cfe4 100644 --- a/src/translator_cn.h +++ b/src/translator_cn.h @@ -543,7 +543,7 @@ class TranslatorChinese : public TranslatorAdapter_1_8_2 if (i!=numEntries-1) // not the last entry, so we need a separator { if (i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/winbuild/doxysearch.vcproj b/winbuild/doxysearch.vcproj new file mode 100644 index 0000000..eb7fb4e --- /dev/null +++ b/winbuild/doxysearch.vcproj @@ -0,0 +1,351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v0.12