]> git.saurik.com Git - wxWidgets.git/blobdiff - utils/HelpGen/src/HelpGen.cpp
added test for HitTest(m_dragPoint) bug to the sample
[wxWidgets.git] / utils / HelpGen / src / HelpGen.cpp
index 0b01804d3804386fa7283040aad648fa5d524035..6828f792526ade66194ee14c6694dfe17c04d8d0 100644 (file)
 /////////////////////////////////////////////////////////////////////////////
 
 /*
 /////////////////////////////////////////////////////////////////////////////
 
 /*
-   TODO (+ means fixed)
+   BUGS
+
+    1. wx/string.h confuses C++ parser terribly
+    2. C++ parser doesn't know about virtual functions, nor static ones
+    3. param checking is not done for vararg functions
+    4. type comparison is dumb: it doesn't know that "char *" is the same
+       that "char []" nor that "const char *" is the same as "char const *"
+
+   TODO (+ means fixed), see also the change log at the end of the file.
 
    (i) small fixes in the current version
 
 
    (i) small fixes in the current version
 
     3. Document global variables
     4. Document #defines
    +5. Program options
     3. Document global variables
     4. Document #defines
    +5. Program options
+    6. Include file name/line number in the "diff" messages?
+   +7. Support for vararg functions
 
    (ii) plans for version 2
     1. Use wxTextFile for direct file access to avoid one scan method problems
 
    (ii) plans for version 2
     1. Use wxTextFile for direct file access to avoid one scan method problems
-    2. Use command line parsrer class for the options
+    2. Use command line parser class for the options
+    3. support for overloaded functions in diff mode (search for OVER)
 
 
+   (iii) plans for version 3
+    1. Merging with existing files
+    2. GUI
 */
 
 // =============================================================================
 */
 
 // =============================================================================
     #include <wx/string.h>
     #include <wx/log.h>
     #include <wx/dynarray.h>
     #include <wx/string.h>
     #include <wx/log.h>
     #include <wx/dynarray.h>
+    #include <wx/wx.h>
 #endif // WX_PRECOMP
 
 #include <wx/file.h>
 #endif // WX_PRECOMP
 
 #include <wx/file.h>
+#include <wx/regex.h>
 
 // C++ parsing classes
 #include "cjparser.h"
 
 // C++ parsing classes
 #include "cjparser.h"
 #include <stdio.h>
 #include <time.h>
 
 #include <stdio.h>
 #include <time.h>
 
+// argh, Windows defines this
+#ifdef GetCurrentTime
+#undef GetCurrentTime
+#endif
+
+// -----------------------------------------------------------------------------
+// global vars
+// -----------------------------------------------------------------------------
+
+class HelpGenApp: public wxApp
+{
+public:
+    HelpGenApp() {};
+
+    // don't let wxWin parse our cmd line, we do it ourselves
+    virtual bool OnInit() { return TRUE; }
+
+    virtual int OnRun();
+};
+
+IMPLEMENT_APP(HelpGenApp);
+
 // -----------------------------------------------------------------------------
 // private functions
 // -----------------------------------------------------------------------------
 // -----------------------------------------------------------------------------
 // private functions
 // -----------------------------------------------------------------------------
@@ -62,20 +100,20 @@ static wxString MakeLabel(const char *classname, const char *funcname = NULL);
 // return the whole \helpref{arg}{arg_label} string
 static wxString MakeHelpref(const char *argument);
 
 // return the whole \helpref{arg}{arg_label} string
 static wxString MakeHelpref(const char *argument);
 
-// quotes special TeX characters in place
+// [un]quote special TeX characters (in place)
 static void TeXFilter(wxString* str);
 static void TeXFilter(wxString* str);
+static void TeXUnfilter(wxString* str); // also trims spaces
 
 // get all comments associated with this context
 static wxString GetAllComments(const spContext& ctx);
 
 // get the string with current time (returns pointer to static buffer)
 // timeFormat is used for the call of strftime(3)
 
 // get all comments associated with this context
 static wxString GetAllComments(const spContext& ctx);
 
 // get the string with current time (returns pointer to static buffer)
 // timeFormat is used for the call of strftime(3)
-#ifdef GetCurrentTime
-#undef GetCurrentTime
-#endif
-
 static const char *GetCurrentTime(const char *timeFormat);
 
 static const char *GetCurrentTime(const char *timeFormat);
 
+// get the string containing the program version
+static const wxString GetVersionString();
+
 // -----------------------------------------------------------------------------
 // private classes
 // -----------------------------------------------------------------------------
 // -----------------------------------------------------------------------------
 // private classes
 // -----------------------------------------------------------------------------
@@ -86,6 +124,14 @@ class wxTeXFile : public wxFile
 public:
     wxTeXFile() { }
 
 public:
     wxTeXFile() { }
 
+    // write a string to file verbatim (should only be used for the strings
+    // inside verbatim environment)
+    bool WriteVerbatim(const wxString& s)
+    {
+        return wxFile::Write(s);
+    }
+
+    // write a string quoting TeX specials in it
     bool WriteTeX(const wxString& s)
     {
         wxString t(s);
     bool WriteTeX(const wxString& s)
     {
         wxString t(s);
@@ -99,15 +145,71 @@ private:
     wxTeXFile& operator=(const wxTeXFile&);
 };
 
     wxTeXFile& operator=(const wxTeXFile&);
 };
 
-class HelpGenVisitor : public spVisitor
+// helper class which manages the classes and function names to ignore for
+// the documentation purposes (used by both HelpGenVisitor and DocManager)
+class IgnoreNamesHandler
 {
 public:
 {
 public:
-    // ctor
-    HelpGenVisitor(const wxString& directoryOut) : m_directoryOut(directoryOut)
+    IgnoreNamesHandler() : m_ignore(CompareIgnoreListEntries) { }
+    ~IgnoreNamesHandler() { WX_CLEAR_ARRAY(m_ignore); }
+
+    // load file with classes/functions to ignore (add them to the names we
+    // already have)
+    bool AddNamesFromFile(const wxString& filename);
+
+    // return TRUE if we ignore this function
+    bool IgnoreMethod(const wxString& classname,
+                      const wxString& funcname) const
     {
     {
-        Reset();
+        if ( IgnoreClass(classname) )
+            return TRUE;
+
+        IgnoreListEntry ignore(classname, funcname);
+
+        return m_ignore.Index(&ignore) != wxNOT_FOUND;
     }
 
     }
 
+    // return TRUE if we ignore this class entirely
+    bool IgnoreClass(const wxString& classname) const
+    {
+        IgnoreListEntry ignore(classname, "");
+
+        return m_ignore.Index(&ignore) != wxNOT_FOUND;
+    }
+
+protected:
+    struct IgnoreListEntry
+    {
+        IgnoreListEntry(const wxString& classname,
+                        const wxString& funcname)
+            : m_classname(classname), m_funcname(funcname)
+        {
+        }
+
+        wxString m_classname;
+        wxString m_funcname;    // if empty, ignore class entirely
+    };
+
+    static int CompareIgnoreListEntries(IgnoreListEntry *first,
+                                        IgnoreListEntry *second);
+
+    // for efficiency, let's sort it
+    WX_DEFINE_SORTED_ARRAY(IgnoreListEntry *, ArrayNamesToIgnore);
+
+    ArrayNamesToIgnore m_ignore;
+
+private:
+    IgnoreNamesHandler(const IgnoreNamesHandler&);
+    IgnoreNamesHandler& operator=(const IgnoreNamesHandler&);
+};
+
+// visitor implementation which writes all collected data to a .tex file
+class HelpGenVisitor : public spVisitor
+{
+public:
+    // ctor
+    HelpGenVisitor(const wxString& directoryOut, bool overwrite);
+
     virtual void VisitFile( spFile& fl );
     virtual void VisitClass( spClass& cl );
     virtual void VisitEnumeration( spEnumeration& en );
     virtual void VisitFile( spFile& fl );
     virtual void VisitClass( spClass& cl );
     virtual void VisitEnumeration( spEnumeration& en );
@@ -119,6 +221,9 @@ public:
 
     void EndVisit();
 
 
     void EndVisit();
 
+    // get our `ignore' object
+    IgnoreNamesHandler& GetIgnoreHandler() { return m_ignoreNames; }
+
     // shut up g++ warning (ain't it stupid?)
     virtual ~HelpGenVisitor() { }
 
     // shut up g++ warning (ain't it stupid?)
     virtual ~HelpGenVisitor() { }
 
@@ -138,7 +243,9 @@ protected:
     // terminate the function documentation if it was started
     void CloseFunction();
 
     // terminate the function documentation if it was started
     void CloseFunction();
 
-    wxString  m_directoryOut;   // directory for the output
+    wxString  m_directoryOut,   // directory for the output
+              m_fileHeader;     // name of the .h file we parse
+    bool      m_overwrite;      // overwrite existing files?
     wxTeXFile m_file;           // file we're writing to now
 
     // state variables
     wxTeXFile m_file;           // file we're writing to now
 
     // state variables
@@ -149,18 +256,187 @@ protected:
          m_inFunction;      // we're parsing a function declaration
 
     // holders for "saved" documentation
          m_inFunction;      // we're parsing a function declaration
 
     // holders for "saved" documentation
-    wxString m_textStoredEnums,
-             m_textStoredTypedefs,
+    wxString m_textStoredTypedefs,
              m_textStoredFunctionComment;
 
              m_textStoredFunctionComment;
 
+    // for enums we have to use an array as we can't intermix the normal text
+    // and the text inside verbatim environment
+    wxArrayString m_storedEnums,
+                  m_storedEnumsVerb;
+
     // headers included by this file
     wxArrayString m_headers;
 
     // headers included by this file
     wxArrayString m_headers;
 
+    // ignore handler: tells us which classes to ignore for doc generation
+    // purposes
+    IgnoreNamesHandler m_ignoreNames;
+
 private:
     HelpGenVisitor(const HelpGenVisitor&);
     HelpGenVisitor& operator=(const HelpGenVisitor&);
 };
 
 private:
     HelpGenVisitor(const HelpGenVisitor&);
     HelpGenVisitor& operator=(const HelpGenVisitor&);
 };
 
+// documentation manager - a class which parses TeX files and remembers the
+// functions documented in them and can later compare them with all functions
+// found under ctxTop by C++ parser
+class DocManager
+{
+public:
+    DocManager(bool checkParamNames);
+    ~DocManager();
+
+    // returns FALSE on failure
+    bool ParseTeXFile(const wxString& filename);
+
+    // returns FALSE if there were any differences
+    bool DumpDifferences(spContext *ctxTop) const;
+
+    // get our `ignore' object
+    IgnoreNamesHandler& GetIgnoreHandler() { return m_ignoreNames; }
+
+protected:
+    // parsing TeX files
+    // -----------------
+
+    // returns the length of 'match' if the string 'str' starts with it or 0
+    // otherwise
+    static size_t TryMatch(const char *str, const char *match);
+
+    // skip spaces: returns pointer to first non space character (also
+    // updates the value of m_line)
+    const char *SkipSpaces(const char *p)
+    {
+        while ( isspace(*p) ) {
+            if ( *p++ == '\n' )
+                m_line++;
+        }
+
+        return p;
+    }
+
+    // skips characters until the next 'c' in '*pp' unless it ends before in
+    // which case FALSE is returned and pp points to '\0', otherwise TRUE is
+    // returned and pp points to 'c'
+    bool SkipUntil(const char **pp, char c);
+
+    // the same as SkipUntil() but only spaces are skipped: on first non space
+    // character different from 'c' the function stops and returns FALSE
+    bool SkipSpaceUntil(const char **pp, char c);
+
+    // extract the string between {} and modify '*pp' to point at the
+    // character immediately after the closing '}'. The returned string is empty
+    // on error.
+    wxString ExtractStringBetweenBraces(const char **pp);
+
+    // the current file and line while we're in ParseTeXFile (for error
+    // messages)
+    wxString m_filename;
+    size_t   m_line;
+
+    // functions and classes to ignore during diff
+    // -------------------------------------------
+
+    IgnoreNamesHandler m_ignoreNames;
+
+    // information about all functions documented in the TeX file(s)
+    // -------------------------------------------------------------
+
+    // info about a type: for now stored as text string, but must be parsed
+    // further later (to know that "char *" == "char []" - TODO)
+    class TypeInfo
+    {
+    public:
+        TypeInfo(const wxString& type) : m_type(type) { }
+
+        bool operator==(const wxString& type) const { return m_type == type; }
+        bool operator!=(const wxString& type) const { return m_type != type; }
+
+        const wxString& GetName() const { return m_type; }
+
+    private:
+        wxString m_type;
+    };
+
+    // info abotu a function parameter
+    class ParamInfo
+    {
+    public:
+        ParamInfo(const wxString& type,
+                  const wxString& name,
+                  const wxString& value)
+            : m_type(type), m_name(name), m_value(value)
+        {
+        }
+
+        const TypeInfo& GetType() const { return m_type; }
+        const wxString& GetName() const { return m_name; }
+        const wxString& GetDefValue() const { return m_value; }
+
+    private:
+        TypeInfo m_type;      // type of parameter
+        wxString m_name;      // name
+        wxString m_value;     // default value
+    };
+
+    WX_DEFINE_ARRAY(ParamInfo *, ArrayParamInfo);
+
+    // info about a function
+    struct MethodInfo
+    {
+    public:
+        enum MethodFlags
+        {
+            Const   = 0x0001,
+            Virtual = 0x0002,
+            Pure    = 0x0004,
+            Static  = 0x0008,
+            Vararg  = 0x0010
+        };
+
+        MethodInfo(const wxString& type,
+                   const wxString& name,
+                   const ArrayParamInfo& params)
+            : m_typeRet(type), m_name(name), m_params(params)
+        {
+            m_flags = 0;
+        }
+
+        void SetFlag(MethodFlags flag) { m_flags |= flag; }
+
+        const TypeInfo& GetType() const { return m_typeRet; }
+        const wxString& GetName() const { return m_name; }
+        const ParamInfo& GetParam(size_t n) const { return *(m_params[n]); }
+        size_t GetParamCount() const { return m_params.GetCount(); }
+
+        bool HasFlag(MethodFlags flag) const { return (m_flags & flag) != 0; }
+
+        ~MethodInfo() { WX_CLEAR_ARRAY(m_params); }
+
+    private:
+        TypeInfo m_typeRet;     // return type
+        wxString m_name;
+        int      m_flags;       // bit mask of the value from the enum above
+
+        ArrayParamInfo m_params;
+    };
+
+    WX_DEFINE_ARRAY(MethodInfo *, ArrayMethodInfo);
+    WX_DEFINE_ARRAY(ArrayMethodInfo *, ArrayMethodInfos);
+
+    // first array contains the names of all classes we found, the second has a
+    // pointer to the array of methods of the given class at the same index as
+    // the class name appears in m_classes
+    wxArrayString    m_classes;
+    ArrayMethodInfos m_methods;
+
+    // are we checking parameter names?
+    bool m_checkParamNames;
+
+private:
+    DocManager(const DocManager&);
+    DocManager& operator=(const DocManager&);
+};
+
 // -----------------------------------------------------------------------------
 // private functions
 // -----------------------------------------------------------------------------
 // -----------------------------------------------------------------------------
 // private functions
 // -----------------------------------------------------------------------------
@@ -172,86 +448,244 @@ private:
 // this function never returns
 static void usage()
 {
 // this function never returns
 static void usage()
 {
-    wxLogError("usage: HelpGen [-q|-v] [-o outdir] <header files...>\n");
+    wxString prog = wxTheApp->argv[0];
+    wxString basename = prog.AfterLast('/');
+#ifdef __WXMSW__
+    if ( !basename )
+        basename = prog.AfterLast('\\');
+#endif
+    if ( !basename )
+        basename = prog;
+
+    wxLogMessage(
+"usage: %s [global options] <mode> [mode options] <files...>\n"
+"\n"
+"   where global options are:\n"
+"       -q          be quiet\n"
+"       -v          be verbose\n"
+"       -H          give this usage message\n"
+"       -V          print the version info\n"
+"       -i file     file with classes/function to ignore\n"
+"\n"
+"   where mode is one of: dump, diff\n"
+"\n"
+"   dump means generate .tex files for TeX2RTF converter from specified\n"
+"   headers files, mode options are:\n"
+"       -f          overwrite existing files\n"
+"       -o outdir   directory for generated files\n"
+"\n"
+"   diff means compare the set of methods documented .tex file with the\n"
+"   methods declared in the header:\n"
+"           %s diff <file.h> <files.tex...>.\n"
+"   mode specific options are:\n"
+"       -p          do check parameter names (not done by default)\n"
+"\n", basename.c_str(), basename.c_str());
 
     exit(1);
 }
 
 
     exit(1);
 }
 
-int main(int argc, char **argv)
+int HelpGenApp::OnRun()
 {
 {
+    enum
+    {
+        Mode_None,
+        Mode_Dump,
+        Mode_Diff
+    } mode = Mode_None;
+
     if ( argc < 2 ) {
         usage();
     }
 
     if ( argc < 2 ) {
         usage();
     }
 
-    wxString directoryOut;
+    wxArrayString filesH, filesTeX;
+    wxString directoryOut,      // directory for 'dmup' output
+             ignoreFile;        // file with classes/functions to ignore
+    bool overwrite = FALSE,     // overwrite existing files during 'dump'?
+         paramNames = FALSE;    // check param names during 'diff'?
 
 
-    int first;
-    for ( first = 1; (first < argc) && argv[first][0] == '-'; first++ ) {
+    for ( int current = 1; current < argc ; current++ ) {
         // all options have one letter
         // all options have one letter
-        if ( argv[first][2] == '\0' ) {
-            switch ( argv[first][1] ) {
-                case 'v':
-                    // be verbose
-                    wxLog::GetActiveTarget()->SetVerbose();
-                    continue;
+        if ( argv[current][0] == '-' ) {
+            if ( argv[current][2] == '\0' ) {
+                switch ( argv[current][1] ) {
+                    case 'v':
+                        // be verbose
+                        wxLog::GetActiveTarget()->SetVerbose();
+                        continue;
+
+                    case 'q':
+                        // be quiet
+                        wxLog::GetActiveTarget()->SetVerbose(FALSE);
+                        continue;
+
+                    case 'H':
+                        // help requested
+                        usage();
+                        // doesn't return
+
+                    case 'V':
+                        // version requested
+                        wxLogMessage("HelpGen version %s\n"
+                                     "(c) 1999-2001 Vadim Zeitlin\n",
+                                     GetVersionString().c_str());
+                        return 0;
+
+                    case 'i':
+                        current++;
+                        if ( current >= argc ) {
+                            wxLogError("-i option requires an argument.");
+
+                            break;
+                        }
 
 
-                case 'q':
-                    // be quiet
-                    wxLog::GetActiveTarget()->SetVerbose(false);
-                    continue;
+                        ignoreFile = argv[current];
+                        continue;
 
 
-                case 'o':
-                    first++;
-                    if ( first >= argc ) {
-                        wxLogError("-o option requires an argument.");
+                    case 'p':
+                        if ( mode != Mode_Diff ) {
+                            wxLogError("-p is only valid with diff.");
 
 
-                        break;
-                    }
+                            break;
+                        }
+
+                        paramNames = TRUE;
+                        continue;
+
+                    case 'f':
+                        if ( mode != Mode_Dump ) {
+                            wxLogError("-f is only valid with dump.");
+
+                            break;
+                        }
+
+                        overwrite = TRUE;
+                        continue;
 
 
-                    directoryOut = argv[first];
-                    if ( !!directoryOut ) {
-                        // terminate with a '/' if it doesn't have it
-                        switch ( directoryOut.Last() ) {
-                            case '/':
+                    case 'o':
+                        if ( mode != Mode_Dump ) {
+                            wxLogError("-o is only valid with dump.");
+
+                            break;
+                        }
+
+                        current++;
+                        if ( current >= argc ) {
+                            wxLogError("-o option requires an argument.");
+
+                            break;
+                        }
+
+                        directoryOut = argv[current];
+                        if ( !!directoryOut ) {
+                            // terminate with a '/' if it doesn't have it
+                            switch ( directoryOut.Last() ) {
+                                case '/':
 #ifdef __WXMSW__
 #ifdef __WXMSW__
-                            case '\\':
+                                case '\\':
 #endif
 #endif
-                                break;
+                                    break;
 
 
-                            default:
-                                directoryOut += '/';
+                                default:
+                                    directoryOut += '/';
+                            }
                         }
                         }
-                    }
-                    //else: it's empty, do nothing
+                        //else: it's empty, do nothing
 
 
-                    continue;
+                        continue;
 
 
-                default:
-                    break;
+                    default:
+                        wxLogError("unknown option '%s'", argv[current]);
+                        break;
+                }
+            }
+            else {
+                wxLogError("only one letter options are allowed, not '%s'.",
+                           argv[current]);
             }
             }
-        }
 
 
-        // only get here after a break from switch or from else branch of if
-        wxLogError("unknown option '%s'", argv[first]);
+            // only get here after a break from switch or from else branch of if
 
 
-        usage();
+            usage();
+        }
+        else {
+            if ( mode == Mode_None ) {
+                if ( strcmp(argv[current], "diff") == 0 )
+                    mode = Mode_Diff;
+                else if ( strcmp(argv[current], "dump") == 0 )
+                    mode = Mode_Dump;
+                else {
+                    wxLogError("unknown mode '%s'.", argv[current]);
+
+                    usage();
+                }
+            }
+            else {
+                if ( mode == Mode_Dump || filesH.IsEmpty() ) {
+                    filesH.Add(argv[current]);
+                }
+                else {
+                    // 2nd files and further are TeX files in diff mode
+                    wxASSERT( mode == Mode_Diff );
+
+                    filesTeX.Add(argv[current]);
+                }
+            }
+        }
     }
 
     // create a parser object and a visitor derivation
     CJSourceParser parser;
     }
 
     // create a parser object and a visitor derivation
     CJSourceParser parser;
-    HelpGenVisitor visitor(directoryOut);
+    HelpGenVisitor visitor(directoryOut, overwrite);
+    if ( !!ignoreFile && mode == Mode_Dump )
+        visitor.GetIgnoreHandler().AddNamesFromFile(ignoreFile);
+
+    spContext *ctxTop = NULL;
 
 
-    // parse all files
-    for ( int i = first; i < argc; i++ ) {
-        spContext *ctxTop = parser.ParseFile(argv[i]);
+    // parse all header files
+    size_t nFiles = filesH.GetCount();
+    for ( size_t n = 0; n < nFiles; n++ ) {
+        wxString header = filesH[n];
+        ctxTop = parser.ParseFile(header);
         if ( !ctxTop ) {
         if ( !ctxTop ) {
-            wxLogWarning("File '%s' couldn't be processed.", argv[i]);
+            wxLogWarning("Header file '%s' couldn't be processed.",
+                         header.c_str());
         }
         }
-        else {
-            ((spFile *)ctxTop)->mFileName = argv[i];
+        else if ( mode == Mode_Dump ) {
+            ((spFile *)ctxTop)->mFileName = header;
             visitor.VisitAll(*ctxTop);
             visitor.EndVisit();
         }
             visitor.VisitAll(*ctxTop);
             visitor.EndVisit();
         }
+
+#ifdef __WXDEBUG__
+        if ( 0 && ctxTop )
+            ctxTop->Dump("");
+#endif // __WXDEBUG__
+    }
+
+    // parse all TeX files
+    if ( mode == Mode_Diff ) {
+        if ( !ctxTop ) {
+            wxLogError("Can't complete diff.");
+
+            // failure
+            return FALSE;
+        }
+
+        DocManager docman(paramNames);
+
+        size_t nFiles = filesTeX.GetCount();
+        for ( size_t n = 0; n < nFiles; n++ ) {
+            wxString file = filesTeX[n];
+            if ( !docman.ParseTeXFile(file) ) {
+                wxLogWarning("TeX file '%s' couldn't be processed.",
+                             file.c_str());
+            }
+        }
+
+        if ( !!ignoreFile )
+            docman.GetIgnoreHandler().AddNamesFromFile(ignoreFile);
+
+        docman.DumpDifferences(ctxTop);
     }
 
     return 0;
     }
 
     return 0;
@@ -261,16 +695,27 @@ int main(int argc, char **argv)
 // HelpGenVisitor implementation
 // -----------------------------------------------------------------------------
 
 // HelpGenVisitor implementation
 // -----------------------------------------------------------------------------
 
+HelpGenVisitor::HelpGenVisitor(const wxString& directoryOut,
+                               bool overwrite)
+              : m_directoryOut(directoryOut)
+{
+    m_overwrite = overwrite;
+
+    Reset();
+}
+
 void HelpGenVisitor::Reset()
 {
     m_inClass =
     m_inFunction =
     m_inTypesSection =
 void HelpGenVisitor::Reset()
 {
     m_inClass =
     m_inFunction =
     m_inTypesSection =
-    m_inMethodSection = false;
+    m_inMethodSection = FALSE;
 
     m_textStoredTypedefs =
 
     m_textStoredTypedefs =
-    m_textStoredEnums =
     m_textStoredFunctionComment = "";
     m_textStoredFunctionComment = "";
+
+    m_storedEnums.Empty();
+    m_storedEnumsVerb.Empty();
     m_headers.Empty();
 }
 
     m_headers.Empty();
 }
 
@@ -282,32 +727,39 @@ void HelpGenVisitor::InsertTypedefDocs()
 
 void HelpGenVisitor::InsertEnumDocs()
 {
 
 void HelpGenVisitor::InsertEnumDocs()
 {
-    m_file.WriteTeX(m_textStoredEnums);
-    m_textStoredEnums.Empty();
+    size_t count = m_storedEnums.GetCount();
+    for ( size_t n = 0; n < count; n++ )
+    {
+        m_file.WriteTeX(m_storedEnums[n]);
+        m_file.WriteVerbatim(m_storedEnumsVerb[n] + '\n');
+    }
+
+    m_storedEnums.Empty();
+    m_storedEnumsVerb.Empty();
 }
 
 void HelpGenVisitor::InsertDataStructuresHeader()
 {
     if ( !m_inTypesSection ) {
 }
 
 void HelpGenVisitor::InsertDataStructuresHeader()
 {
     if ( !m_inTypesSection ) {
-        m_inTypesSection = true;
+        m_inTypesSection = TRUE;
 
 
-        m_file.WriteTeX("\\wxheading{Data structures}\n\n");
+        m_file.Write("\\wxheading{Data structures}\n\n");
     }
 }
 
 void HelpGenVisitor::InsertMethodsHeader()
 {
     if ( !m_inMethodSection ) {
     }
 }
 
 void HelpGenVisitor::InsertMethodsHeader()
 {
     if ( !m_inMethodSection ) {
-        m_inMethodSection = true;
+        m_inMethodSection = TRUE;
 
 
-        m_file.WriteTeX( "\\latexignore{\\rtfignore{\\wxheading{Members}}}\n\n");
+        m_file.Write( "\\latexignore{\\rtfignore{\\wxheading{Members}}}\n\n");
     }
 }
 
 void HelpGenVisitor::CloseFunction()
 {
     if ( m_inFunction ) {
     }
 }
 
 void HelpGenVisitor::CloseFunction()
 {
     if ( m_inFunction ) {
-        m_inFunction = false;
+        m_inFunction = FALSE;
 
         wxString totalText;
         if ( m_isFirstParam ) {
 
         wxString totalText;
         if ( m_isFirstParam ) {
@@ -316,11 +768,10 @@ void HelpGenVisitor::CloseFunction()
         }
 
         totalText << "}\n\n";
         }
 
         totalText << "}\n\n";
+        m_file.Write(totalText);
 
         if ( !m_textStoredFunctionComment.IsEmpty() )
 
         if ( !m_textStoredFunctionComment.IsEmpty() )
-            totalText << m_textStoredFunctionComment << '\n';
-
-        m_file.WriteTeX(totalText);
+            m_file.WriteTeX(m_textStoredFunctionComment + '\n');
     }
 }
 
     }
 }
 
@@ -328,23 +779,34 @@ void HelpGenVisitor::EndVisit()
 {
     CloseFunction();
 
 {
     CloseFunction();
 
-    wxLogInfo("%s: finished parsing the current file.",
-              GetCurrentTime("%H:%M:%S"));
+    m_fileHeader.Empty();
+
+    wxLogVerbose("%s: finished generating for the current file.",
+                 GetCurrentTime("%H:%M:%S"));
 }
 
 void HelpGenVisitor::VisitFile( spFile& file )
 {
 }
 
 void HelpGenVisitor::VisitFile( spFile& file )
 {
-    wxLogInfo("%s: started to parse classes from file '%s'...",
-              GetCurrentTime("%H:%M:%S"), file.mFileName.c_str());
+    m_fileHeader = file.mFileName;
+    wxLogVerbose("%s: started generating docs for classes from file '%s'...",
+                 GetCurrentTime("%H:%M:%S"), m_fileHeader.c_str());
 }
 
 void HelpGenVisitor::VisitClass( spClass& cl )
 {
 }
 
 void HelpGenVisitor::VisitClass( spClass& cl )
 {
+    m_inClass = FALSE; // will be left FALSE on error
+
     wxString name = cl.GetName();
 
     wxString name = cl.GetName();
 
+    if ( m_ignoreNames.IgnoreClass(name) ) {
+        wxLogVerbose("Skipping ignored class '%s'.", name.c_str());
+
+        return;
+    }
+
     // the file name is built from the class name by removing the leading "wx"
     // if any and converting it to the lower case
     // the file name is built from the class name by removing the leading "wx"
     // if any and converting it to the lower case
-    wxString filename = m_directoryOut;
+    wxString filename;
     if ( name(0, 2) == "wx" ) {
         filename << name.c_str() + 2;
     }
     if ( name(0, 2) == "wx" ) {
         filename << name.c_str() + 2;
     }
@@ -354,6 +816,14 @@ void HelpGenVisitor::VisitClass( spClass& cl )
 
     filename.MakeLower();
     filename += ".tex";
 
     filename.MakeLower();
     filename += ".tex";
+    filename.Prepend(m_directoryOut);
+
+    if ( !m_overwrite && wxFile::Exists(filename) ) {
+        wxLogError("Won't overwrite existing file '%s' - please use '-f'.",
+                   filename.c_str());
+
+        return;
+    }
 
     m_inClass = m_file.Open(filename, wxFile::write);
     if ( !m_inClass ) {
 
     m_inClass = m_file.Open(filename, wxFile::write);
     if ( !m_inClass ) {
@@ -364,29 +834,30 @@ void HelpGenVisitor::VisitClass( spClass& cl )
     }
 
     m_inMethodSection =
     }
 
     m_inMethodSection =
-    m_inTypesSection = false;
+    m_inTypesSection = FALSE;
 
     wxLogInfo("Created new file '%s' for class '%s'.",
               filename.c_str(), name.c_str());
 
 
     wxLogInfo("Created new file '%s' for class '%s'.",
               filename.c_str(), name.c_str());
 
-    // the entire text we're writing to file
-    wxString totalText;
-
     // write out the header
     // write out the header
-    {
-        wxString header;
-        header.Printf("%%\n"
-                      "%% automatically generated by HelpGen from\n"
-                      "%% %s at %s\n"
-                      "%%\n"
-                      "\n"
-                      "\n"
-                      "\\section{\\class{%s}}\\label{%s}\n",
-                      filename.c_str(), GetCurrentTime("%d/%b/%y %H:%M:%S"),
-                      name.c_str(), wxString(name).MakeLower().c_str());
+    wxString header;
+    header.Printf("%%\n"
+                  "%% automatically generated by HelpGen %s from\n"
+                  "%% %s at %s\n"
+                  "%%\n"
+                  "\n"
+                  "\n"
+                  "\\section{\\class{%s}}\\label{%s}\n\n",
+                  GetVersionString().c_str(),
+                  m_fileHeader.c_str(),
+                  GetCurrentTime("%d/%b/%y %H:%M:%S"),
+                  name.c_str(),
+                  wxString(name).MakeLower().c_str());
+
+    m_file.Write(header);
 
 
-        totalText << header << '\n';
-    }
+    // the entire text we're writing to file
+    wxString totalText;
 
     // if the header includes other headers they must be related to it... try to
     // automatically generate the "See also" clause
 
     // if the header includes other headers they must be related to it... try to
     // automatically generate the "See also" clause
@@ -467,7 +938,7 @@ void HelpGenVisitor::VisitClass( spClass& cl )
         derived << "No base class";
     }
     else {
         derived << "No base class";
     }
     else {
-        bool first = true;
+        bool first = TRUE;
         for ( StrListT::const_iterator i = baseClasses.begin();
               i != baseClasses.end();
               i++ ) {
         for ( StrListT::const_iterator i = baseClasses.begin();
               i != baseClasses.end();
               i++ ) {
@@ -476,7 +947,7 @@ void HelpGenVisitor::VisitClass( spClass& cl )
                 derived << "\\\\\n";
             }
             else {
                 derived << "\\\\\n";
             }
             else {
-                first = false;
+                first = FALSE;
             }
 
             wxString baseclass = *i;
             }
 
             wxString baseclass = *i;
@@ -509,25 +980,25 @@ void HelpGenVisitor::VisitEnumeration( spEnumeration& en )
     }
 
     // simply copy the enum text in the docs
     }
 
     // simply copy the enum text in the docs
-    wxString enumeration = GetAllComments(en);
-    enumeration << "{\\small \\begin{verbatim}\n"
-                << en.mEnumContent
-                << "\n\\end{verbatim}}\n";
+    wxString enumeration = GetAllComments(en),
+             enumerationVerb;
+
+    enumerationVerb << "\\begin{verbatim}\n"
+                    << en.mEnumContent
+                    << "\n\\end{verbatim}\n";
 
     // remember for later use if we're not inside a class yet
     if ( !m_inClass ) {
 
     // remember for later use if we're not inside a class yet
     if ( !m_inClass ) {
-        if ( !m_textStoredEnums.IsEmpty() ) {
-            m_textStoredEnums << '\n';
-        }
-
-        m_textStoredEnums << enumeration;
+        m_storedEnums.Add(enumeration);
+        m_storedEnumsVerb.Add(enumerationVerb);
     }
     else {
         // write the header for this section if not done yet
         InsertDataStructuresHeader();
 
     }
     else {
         // write the header for this section if not done yet
         InsertDataStructuresHeader();
 
-        enumeration << '\n';
         m_file.WriteTeX(enumeration);
         m_file.WriteTeX(enumeration);
+        m_file.WriteVerbatim(enumerationVerb);
+        m_file.Write('\n');
     }
 }
 
     }
 }
 
@@ -593,8 +1064,14 @@ void HelpGenVisitor::VisitOperation( spOperation& op )
 {
     CloseFunction();
 
 {
     CloseFunction();
 
-    if ( !m_inClass || !op.IsInClass() ) {
-        // FIXME that's a bug too
+    if ( !m_inClass ) {
+        // we don't generate docs right now - either we ignore this class
+        // entirely or we couldn't open the file
+        return;
+    }
+
+    if ( !op.IsInClass() ) {
+        // TODO document global functions
         wxLogWarning("skipped global function '%s'.", op.GetName().c_str());
 
         return;
         wxLogWarning("skipped global function '%s'.", op.GetName().c_str());
 
         return;
@@ -605,23 +1082,30 @@ void HelpGenVisitor::VisitOperation( spOperation& op )
         return;
     }
 
         return;
     }
 
+    wxString funcname = op.GetName(),
+             classname = op.GetClass().GetName();
+    if ( m_ignoreNames.IgnoreMethod(classname, funcname) ) {
+        wxLogVerbose("Skipping ignored '%s::%s'.",
+                     classname.c_str(), funcname.c_str());
+
+        return;
+    }
+
     InsertMethodsHeader();
 
     // save state info
     m_inFunction =
     InsertMethodsHeader();
 
     // save state info
     m_inFunction =
-    m_isFirstParam = true;
+    m_isFirstParam = TRUE;
 
     m_textStoredFunctionComment = GetAllComments(op);
 
     // start function documentation
     wxString totalText;
 
     m_textStoredFunctionComment = GetAllComments(op);
 
     // start function documentation
     wxString totalText;
-    const char *funcname = op.GetName().c_str();
-    const char *classname = op.GetClass().GetName().c_str();
 
     // check for the special case of dtor
     wxString dtor;
 
     // check for the special case of dtor
     wxString dtor;
-    if ( (funcname[0] == '~') && (strcmp(funcname + 1, classname) == 0) ) {
-        dtor.Printf("\\destruct{%s}", classname);
+    if ( (funcname[0] == '~') && (classname == funcname.c_str() + 1) ) {
+        dtor.Printf("\\destruct{%s}", classname.c_str());
         funcname = dtor;
     }
 
         funcname = dtor;
     }
 
@@ -629,12 +1113,12 @@ void HelpGenVisitor::VisitOperation( spOperation& op )
                      "\\membersection{%s::%s}\\label{%s}\n"
                      "\n"
                      "\\%sfunc{%s%s}{%s}{",
                      "\\membersection{%s::%s}\\label{%s}\n"
                      "\n"
                      "\\%sfunc{%s%s}{%s}{",
-                     classname, funcname,
+                     classname.c_str(), funcname.c_str(),
                      MakeLabel(classname, funcname).c_str(),
                      op.mIsConstant ? "const" : "",
                      op.mIsVirtual ? "virtual " : "",
                      op.mRetType.c_str(),
                      MakeLabel(classname, funcname).c_str(),
                      op.mIsConstant ? "const" : "",
                      op.mIsVirtual ? "virtual " : "",
                      op.mRetType.c_str(),
-                     funcname);
+                     funcname.c_str());
 
     m_file.WriteTeX(totalText);
 }
 
     m_file.WriteTeX(totalText);
 }
@@ -646,7 +1130,7 @@ void HelpGenVisitor::VisitParameter( spParameter& param )
 
     wxString totalText;
     if ( m_isFirstParam ) {
 
     wxString totalText;
     if ( m_isFirstParam ) {
-        m_isFirstParam = false;
+        m_isFirstParam = FALSE;
     }
     else {
         totalText << ", ";
     }
     else {
         totalText << ", ";
@@ -663,6 +1147,717 @@ void HelpGenVisitor::VisitParameter( spParameter& param )
     m_file.WriteTeX(totalText);
 }
 
     m_file.WriteTeX(totalText);
 }
 
+// ---------------------------------------------------------------------------
+// DocManager
+// ---------------------------------------------------------------------------
+
+DocManager::DocManager(bool checkParamNames)
+{
+    m_checkParamNames = checkParamNames;
+}
+
+size_t DocManager::TryMatch(const char *str, const char *match)
+{
+    size_t lenMatch = 0;
+    while ( str[lenMatch] == match[lenMatch] ) {
+        lenMatch++;
+
+        if ( match[lenMatch] == '\0' )
+            return lenMatch;
+    }
+
+    return 0;
+}
+
+bool DocManager::SkipUntil(const char **pp, char c)
+{
+    const char *p = *pp;
+    while ( *p != c ) {
+        if ( *p == '\0' )
+            break;
+
+        if ( *p == '\n' )
+            m_line++;
+
+        p++;
+    }
+
+    *pp = p;
+
+    return *p == c;
+}
+
+bool DocManager::SkipSpaceUntil(const char **pp, char c)
+{
+    const char *p = *pp;
+    while ( *p != c ) {
+        if ( !isspace(*p) || *p == '\0' )
+            break;
+
+        if ( *p == '\n' )
+            m_line++;
+
+        p++;
+    }
+
+    *pp = p;
+
+    return *p == c;
+}
+
+wxString DocManager::ExtractStringBetweenBraces(const char **pp)
+{
+    wxString result;
+
+    if ( !SkipSpaceUntil(pp, '{') ) {
+        wxLogWarning("file %s(%d): '{' expected after '\\param'",
+                     m_filename.c_str(), m_line);
+
+    }
+    else {
+        const char *startParam = ++*pp; // skip '{'
+
+        if ( !SkipUntil(pp, '}') ) {
+            wxLogWarning("file %s(%d): '}' expected after '\\param'",
+                         m_filename.c_str(), m_line);
+        }
+        else {
+            result = wxString(startParam, (*pp)++ - startParam);
+        }
+    }
+
+    return result;
+}
+
+bool DocManager::ParseTeXFile(const wxString& filename)
+{
+    m_filename = filename;
+
+    wxFile file(m_filename, wxFile::read);
+    if ( !file.IsOpened() )
+        return FALSE;
+
+    off_t len = file.Length();
+    if ( len == wxInvalidOffset )
+        return FALSE;
+
+    char *buf = new char[len + 1];
+    buf[len] = '\0';
+
+    if ( file.Read(buf, len) == wxInvalidOffset ) {
+        delete [] buf;
+
+        return FALSE;
+    }
+
+    // reinit everything
+    m_line = 1;
+
+    wxLogVerbose("%s: starting to parse doc file '%s'.",
+                 GetCurrentTime("%H:%M:%S"), m_filename.c_str());
+
+    // the name of the class from the last "\membersection" command: we assume
+    // that the following "\func" or "\constfunc" always documents a method of
+    // this class (and it should always be like that in wxWindows documentation)
+    wxString classname;
+
+    for ( const char *current = buf; current - buf < len; current++ ) {
+        // FIXME parsing is awfully inefficient
+
+        if ( *current == '%' ) {
+            // comment, skip until the end of line
+            current++;
+            SkipUntil(&current, '\n');
+
+            continue;
+        }
+
+        // all the command we're interested in start with '\\'
+        while ( *current != '\\' && *current != '\0' ) {
+            if ( *current++ == '\n' )
+                m_line++;
+        }
+
+        if ( *current == '\0' ) {
+            // no more TeX commands left
+            break;
+        }
+
+        current++; // skip '\\'
+
+        enum
+        {
+            Nothing,
+            Func,
+            ConstFunc,
+            MemberSect
+        } foundCommand = Nothing;
+
+        size_t lenMatch = TryMatch(current, "func");
+        if ( lenMatch ) {
+            foundCommand = Func;
+        }
+        else {
+            lenMatch = TryMatch(current, "constfunc");
+            if ( lenMatch )
+                foundCommand = ConstFunc;
+            else {
+                lenMatch = TryMatch(current, "membersection");
+
+                if ( lenMatch )
+                    foundCommand = MemberSect;
+            }
+        }
+
+        if ( foundCommand == Nothing )
+            continue;
+
+        current += lenMatch;
+
+        if ( !SkipSpaceUntil(&current, '{') ) {
+            wxLogWarning("file %s(%d): '{' expected after \\func, "
+                         "\\constfunc or \\membersection.",
+                         m_filename.c_str(), m_line);
+
+            continue;
+        }
+
+        current++;
+
+        if ( foundCommand == MemberSect ) {
+            // what follows has the form <classname>::<funcname>
+            const char *startClass = current;
+            if ( !SkipUntil(&current, ':') || *(current + 1) != ':' ) {
+                wxLogWarning("file %s(%d): '::' expected after "
+                             "\\membersection.", m_filename.c_str(), m_line);
+            }
+            else {
+                classname = wxString(startClass, current - startClass);
+                TeXUnfilter(&classname);
+            }
+
+            continue;
+        }
+
+        // extract the return type
+        const char *startRetType = current;
+
+        if ( !SkipUntil(&current, '}') ) {
+            wxLogWarning("file %s(%d): '}' expected after return type",
+                         m_filename.c_str(), m_line);
+
+            continue;
+        }
+
+        wxString returnType = wxString(startRetType, current - startRetType);
+        TeXUnfilter(&returnType);
+
+        current++;
+        if ( !SkipSpaceUntil(&current, '{') ) { 
+            wxLogWarning("file %s(%d): '{' expected after return type",
+                         m_filename.c_str(), m_line);
+
+            continue;
+        }
+
+        current++;
+        const char *funcEnd = current;
+        if ( !SkipUntil(&funcEnd, '}') ) {
+            wxLogWarning("file %s(%d): '}' expected after function name",
+                         m_filename.c_str(), m_line);
+
+            continue;
+        }
+
+        wxString funcName = wxString(current, funcEnd - current);
+        current = funcEnd + 1;
+
+        // trim spaces from both sides
+        funcName.Trim(FALSE);
+        funcName.Trim(TRUE);
+
+        // special cases: '$...$' may be used for LaTeX inline math, remove the
+        // '$'s
+        if ( funcName.Find('$') != wxNOT_FOUND ) {
+            wxString name;
+            for ( const char *p = funcName.c_str(); *p != '\0'; p++ ) {
+                if ( *p != '$' && !isspace(*p) )
+                    name += *p;
+            }
+
+            funcName = name;
+        }
+
+        // \destruct{foo} is really ~foo
+        if ( funcName[0u] == '\\' ) {
+            size_t len = strlen("\\destruct{");
+            if ( funcName(0, len) != "\\destruct{" ) {
+                wxLogWarning("file %s(%d): \\destruct expected",
+                             m_filename.c_str(), m_line);
+
+                continue;
+            }
+
+            funcName.erase(0, len);
+            funcName.Prepend('~');
+
+            if ( !SkipSpaceUntil(&current, '}') ) {
+                wxLogWarning("file %s(%d): '}' expected after destructor",
+                             m_filename.c_str(), m_line);
+
+                continue;
+            }
+
+            funcEnd++;  // there is an extra '}' to count
+        }
+
+        TeXUnfilter(&funcName);
+
+        // extract params
+        current = funcEnd + 1; // skip '}'
+        if ( !SkipSpaceUntil(&current, '{') ||
+             (current++, !SkipSpaceUntil(&current, '\\')) ) {
+            wxLogWarning("file %s(%d): '\\param' or '\\void' expected",
+                         m_filename.c_str(), m_line);
+
+            continue;
+        }
+
+        wxArrayString paramNames, paramTypes, paramValues;
+
+        bool isVararg = FALSE;
+
+        current++; // skip '\\'
+        lenMatch = TryMatch(current, "void");
+        if ( !lenMatch ) {
+            lenMatch = TryMatch(current, "param");
+            while ( lenMatch && (current - buf < len) ) {
+                current += lenMatch;
+
+                // now come {paramtype}{paramname}
+                wxString paramType = ExtractStringBetweenBraces(&current);
+                if ( !!paramType ) {
+                    wxString paramText = ExtractStringBetweenBraces(&current);
+                    if ( !!paramText ) {
+                        // the param declaration may contain default value
+                        wxString paramName = paramText.BeforeFirst('='),
+                                 paramValue = paramText.AfterFirst('=');
+
+                        // sanitize all strings
+                        TeXUnfilter(&paramValue);
+                        TeXUnfilter(&paramName);
+                        TeXUnfilter(&paramType);
+
+                        paramValues.Add(paramValue);
+                        paramNames.Add(paramName);
+                        paramTypes.Add(paramType);
+                    }
+                }
+                else {
+                    // vararg function?
+                    wxString paramText = ExtractStringBetweenBraces(&current);
+                    if ( paramText == "..." ) {
+                        isVararg = TRUE;
+                    }
+                    else {
+                        wxLogWarning("Parameters of '%s::%s' are in "
+                                     "incorrect form.",
+                                     classname.c_str(), funcName.c_str());
+                    }
+                }
+
+                // what's next?
+                current = SkipSpaces(current);
+                if ( *current == ',' || *current == '}' ) {
+                    current = SkipSpaces(++current);
+
+                    lenMatch = TryMatch(current, "\\param");
+                }
+                else {
+                    wxLogWarning("file %s(%d): ',' or '}' expected after "
+                                 "'\\param'", m_filename.c_str(), m_line);
+
+                    continue;
+                }
+            }
+
+            // if we got here there was no '\\void', so must have some params
+            if ( paramNames.IsEmpty() ) {
+                wxLogWarning("file %s(%d): '\\param' or '\\void' expected",
+                        m_filename.c_str(), m_line);
+
+                continue;
+            }
+        }
+
+        // verbose diagnostic output
+        wxString paramsAll;
+        size_t param, paramCount = paramNames.GetCount();
+        for ( param = 0; param < paramCount; param++ ) {
+            if ( param != 0 ) {
+                paramsAll << ", ";
+            }
+
+            paramsAll << paramTypes[param] << ' ' << paramNames[param];
+        }
+
+        wxLogVerbose("file %s(%d): found '%s %s::%s(%s)%s'",
+                     m_filename.c_str(), m_line,
+                     returnType.c_str(),
+                     classname.c_str(),
+                     funcName.c_str(),
+                     paramsAll.c_str(),
+                     foundCommand == ConstFunc ? " const" : "");
+
+        // store the info about the just found function
+        ArrayMethodInfo *methods;
+        int index = m_classes.Index(classname);
+        if ( index == wxNOT_FOUND ) {
+            m_classes.Add(classname);
+
+            methods = new ArrayMethodInfo;
+            m_methods.Add(methods);
+        }
+        else {
+            methods = m_methods[(size_t)index];
+        }
+
+        ArrayParamInfo params;
+        for ( param = 0; param < paramCount; param++ ) {
+            params.Add(new ParamInfo(paramTypes[param],
+                                     paramNames[param],
+                                     paramValues[param]));
+        }
+
+        MethodInfo *method = new MethodInfo(returnType, funcName, params);
+        if ( foundCommand == ConstFunc )
+            method->SetFlag(MethodInfo::Const);
+        if ( isVararg )
+            method->SetFlag(MethodInfo::Vararg);
+
+        methods->Add(method);
+    }
+
+    delete [] buf;
+
+    wxLogVerbose("%s: finished parsing doc file '%s'.\n",
+                 GetCurrentTime("%H:%M:%S"), m_filename.c_str());
+
+    return TRUE;
+}
+
+bool DocManager::DumpDifferences(spContext *ctxTop) const
+{
+    typedef MMemberListT::const_iterator MemberIndex;
+
+    bool foundDiff = FALSE;
+
+    // flag telling us whether the given class was found at all in the header
+    size_t nClass, countClassesInDocs = m_classes.GetCount();
+    bool *classExists = new bool[countClassesInDocs];
+    for ( nClass = 0; nClass < countClassesInDocs; nClass++ ) {
+        classExists[nClass] = FALSE;
+    }
+
+    // ctxTop is normally an spFile
+    wxASSERT( ctxTop->GetContextType() == SP_CTX_FILE );
+
+    const MMemberListT& classes = ctxTop->GetMembers();
+    for ( MemberIndex i = classes.begin(); i != classes.end(); i++ ) {
+        spContext *ctx = *i;
+        if ( ctx->GetContextType() != SP_CTX_CLASS ) {
+            // TODO process also global functions, macros, ...
+            continue;
+        }
+
+        spClass *ctxClass = (spClass *)ctx;
+        const wxString& nameClass = ctxClass->mName;
+        int index = m_classes.Index(nameClass);
+        if ( index == wxNOT_FOUND ) {
+            if ( !m_ignoreNames.IgnoreClass(nameClass) ) {
+                foundDiff = TRUE;
+
+                wxLogError("Class '%s' is not documented at all.",
+                           nameClass.c_str());
+            }
+
+            // it makes no sense to check for its functions
+            continue;
+        }
+        else {
+            classExists[index] = TRUE;
+        }
+
+        // array of method descriptions for this class
+        const ArrayMethodInfo& methods = *(m_methods[index]);
+        size_t nMethod, countMethods = methods.GetCount();
+
+        // flags telling if we already processed given function
+        bool *methodExists = new bool[countMethods];
+        for ( nMethod = 0; nMethod < countMethods; nMethod++ ) {
+            methodExists[nMethod] = FALSE;
+        }
+
+        wxArrayString aOverloadedMethods;
+
+        const MMemberListT& functions = ctxClass->GetMembers();
+        for ( MemberIndex j = functions.begin(); j != functions.end(); j++ ) {
+            ctx = *j;
+            if ( ctx->GetContextType() != SP_CTX_OPERATION )
+                continue;
+
+            spOperation *ctxMethod = (spOperation *)ctx;
+            const wxString& nameMethod = ctxMethod->mName;
+
+            // find all functions with the same name
+            wxArrayInt aMethodsWithSameName;
+            for ( nMethod = 0; nMethod < countMethods; nMethod++ ) {
+                if ( methods[nMethod]->GetName() == nameMethod )
+                    aMethodsWithSameName.Add(nMethod);
+            }
+
+            if ( aMethodsWithSameName.IsEmpty() && ctxMethod->IsPublic() ) {
+                if ( !m_ignoreNames.IgnoreMethod(nameClass, nameMethod) ) {
+                    foundDiff = TRUE;
+
+                    wxLogError("'%s::%s' is not documented.",
+                               nameClass.c_str(),
+                               nameMethod.c_str());
+                }
+
+                // don't check params
+                continue;
+            }
+            else if ( aMethodsWithSameName.GetCount() == 1 ) {
+                index = (size_t)aMethodsWithSameName[0u];
+                methodExists[index] = TRUE;
+
+                if ( m_ignoreNames.IgnoreMethod(nameClass, nameMethod) )
+                    continue;
+
+                if ( !ctxMethod->IsPublic() ) {
+                    wxLogWarning("'%s::%s' is documented but not public.",
+                                 nameClass.c_str(),
+                                 nameMethod.c_str());
+                }
+
+                // check that the flags match
+                const MethodInfo& method = *(methods[index]);
+
+                bool isVirtual = ctxMethod->mIsVirtual;
+                if ( isVirtual != method.HasFlag(MethodInfo::Virtual) ) {
+                    wxLogWarning("'%s::%s' is incorrectly documented as %s"
+                                 "virtual.",
+                                 nameClass.c_str(),
+                                 nameMethod.c_str(),
+                                 isVirtual ? "not " : "");
+                }
+
+                bool isConst = ctxMethod->mIsConstant;
+                if ( isConst != method.HasFlag(MethodInfo::Const) ) {
+                    wxLogWarning("'%s::%s' is incorrectly documented as %s"
+                                 "constant.",
+                                 nameClass.c_str(),
+                                 nameMethod.c_str(),
+                                 isConst ? "not " : "");
+                }
+
+                // check that the params match
+                const MMemberListT& params = ctxMethod->GetMembers();
+
+                if ( params.size() != method.GetParamCount() ) {
+                    wxLogError("Incorrect number of parameters for '%s::%s' "
+                               "in the docs: should be %d instead of %d.",
+                               nameClass.c_str(),
+                               nameMethod.c_str(),
+                               params.size(), method.GetParamCount());
+                }
+                else {
+                    size_t nParam = 0;
+                    for ( MemberIndex k = params.begin();
+                          k != params.end();
+                          k++, nParam++ ) {
+                        ctx = *k;
+
+                        // what else can a function have?
+                        wxASSERT( ctx->GetContextType() == SP_CTX_PARAMETER );
+
+                        spParameter *ctxParam = (spParameter *)ctx;
+                        const ParamInfo& param = method.GetParam(nParam);
+                        if ( m_checkParamNames &&
+                             (param.GetName() != ctxParam->mName) ) {
+                            foundDiff = TRUE;
+
+                            wxLogError("Parameter #%d of '%s::%s' should be "
+                                       "'%s' and not '%s'.",
+                                       nParam + 1,
+                                       nameClass.c_str(),
+                                       nameMethod.c_str(),
+                                       ctxParam->mName.c_str(),
+                                       param.GetName().c_str());
+
+                            continue;
+                        }
+
+                        if ( param.GetType() != ctxParam->mType ) {
+                            foundDiff = TRUE;
+
+                            wxLogError("Type of parameter '%s' of '%s::%s' "
+                                       "should be '%s' and not '%s'.",
+                                       ctxParam->mName.c_str(),
+                                       nameClass.c_str(),
+                                       nameMethod.c_str(),
+                                       ctxParam->mType.c_str(),
+                                       param.GetType().GetName().c_str());
+
+                            continue;
+                        }
+
+                        if ( param.GetDefValue() != ctxParam->mInitVal ) {
+                            wxLogWarning("Default value of parameter '%s' of "
+                                         "'%s::%s' should be '%s' and not "
+                                         "'%s'.",
+                                         ctxParam->mName.c_str(),
+                                         nameClass.c_str(),
+                                         nameMethod.c_str(),
+                                         ctxParam->mInitVal.c_str(),
+                                         param.GetDefValue().c_str());
+                        }
+                    }
+                }
+            }
+            else {
+                // TODO OVER add real support for overloaded methods
+
+                if ( m_ignoreNames.IgnoreMethod(nameClass, nameMethod) )
+                    continue;
+
+                if ( aOverloadedMethods.Index(nameMethod) == wxNOT_FOUND ) {
+                    // mark all methods with this name as existing
+                    for ( nMethod = 0; nMethod < countMethods; nMethod++ ) {
+                        if ( methods[nMethod]->GetName() == nameMethod )
+                            methodExists[nMethod] = TRUE;
+                    }
+
+                    aOverloadedMethods.Add(nameMethod);
+
+                    wxLogVerbose("'%s::%s' is overloaded and I'm too "
+                                 "stupid to find the right match - skipping "
+                                 "the param and flags checks.",
+                                 nameClass.c_str(),
+                                 nameMethod.c_str());
+                }
+                //else: warning already given
+            }
+        }
+
+        for ( nMethod = 0; nMethod < countMethods; nMethod++ ) {
+            if ( !methodExists[nMethod] ) {
+                const wxString& nameMethod = methods[nMethod]->GetName();
+                if ( !m_ignoreNames.IgnoreMethod(nameClass, nameMethod) ) {
+                    foundDiff = TRUE;
+
+                    wxLogError("'%s::%s' is documented but doesn't exist.",
+                               nameClass.c_str(),
+                               nameMethod.c_str());
+                }
+            }
+        }
+
+        delete [] methodExists;
+    }
+
+    // check that all classes we found in the docs really exist
+    for ( nClass = 0; nClass < countClassesInDocs; nClass++ ) {
+        if ( !classExists[nClass] ) {
+            foundDiff = TRUE;
+
+            wxLogError("Class '%s' is documented but doesn't exist.",
+                       m_classes[nClass].c_str());
+        }
+    }
+
+    delete [] classExists;
+
+    return !foundDiff;
+}
+
+DocManager::~DocManager()
+{
+    WX_CLEAR_ARRAY(m_methods);
+}
+
+// ---------------------------------------------------------------------------
+// IgnoreNamesHandler implementation
+// ---------------------------------------------------------------------------
+
+int IgnoreNamesHandler::CompareIgnoreListEntries(IgnoreListEntry *first,
+                                                 IgnoreListEntry *second)
+{
+    // first compare the classes
+    int rc = first->m_classname.Cmp(second->m_classname);
+    if ( rc == 0 )
+        rc = first->m_funcname.Cmp(second->m_funcname);
+
+    return rc;
+}
+
+bool IgnoreNamesHandler::AddNamesFromFile(const wxString& filename)
+{
+    wxFile file(filename, wxFile::read);
+    if ( !file.IsOpened() )
+        return FALSE;
+
+    off_t len = file.Length();
+    if ( len == wxInvalidOffset )
+        return FALSE;
+
+    char *buf = new char[len + 1];
+    buf[len] = '\0';
+
+    if ( file.Read(buf, len) == wxInvalidOffset ) {
+        delete [] buf;
+
+        return FALSE;
+    }
+
+    wxString line;
+    for ( const char *current = buf; ; current++ ) {
+#ifdef __WXMSW__
+        // skip DOS line separator
+        if ( *current == '\r' )
+            current++;
+#endif // wxMSW
+
+        if ( *current == '\n' || *current == '\0' ) {
+            if ( line[0u] != '#' ) {
+                if ( line.Find(':') != wxNOT_FOUND ) {
+                    wxString classname = line.BeforeFirst(':'),
+                             funcname = line.AfterLast(':');
+                    m_ignore.Add(new IgnoreListEntry(classname, funcname));
+                }
+                else {
+                    // entire class
+                    m_ignore.Add(new IgnoreListEntry(line, ""));
+                }
+            }
+            //else: comment
+
+            if ( *current == '\0' )
+                break;
+
+            line.Empty();
+        }
+        else {
+            line += *current;
+        }
+    }
+
+    delete [] buf;
+
+    return TRUE;
+}
+
 // -----------------------------------------------------------------------------
 // global function implementation
 // -----------------------------------------------------------------------------
 // -----------------------------------------------------------------------------
 // global function implementation
 // -----------------------------------------------------------------------------
@@ -710,10 +1905,32 @@ static wxString MakeHelpref(const char *argument)
 }
 
 static void TeXFilter(wxString* str)
 }
 
 static void TeXFilter(wxString* str)
+{
+    // TeX special which can be quoted (don't include backslash nor braces as
+    // we generate them 
+    static wxRegEx reNonSpecialSpecials("[#$%&_]"),
+                   reAccents("[~^]");
+
+    // just quote
+    reNonSpecialSpecials.ReplaceAll(str, "\\\\\\0");
+
+    // can't quote these ones as they produce accents when preceded by
+    // backslash, so put them inside verb
+    reAccents.ReplaceAll(str, "\\\\verb|\\0|");
+}
+
+static void TeXUnfilter(wxString* str)
 {
     // FIXME may be done much more quickly
 {
     // FIXME may be done much more quickly
-    str->Replace("&", "\\&");
-    str->Replace("_", "\\_");
+    str->Trim(TRUE);
+    str->Trim(FALSE);
+
+    // undo TeXFilter
+    static wxRegEx reNonSpecialSpecials("\\\\([#$%&_{}])"),
+                   reAccents("\\\\verb|([~^])|");
+
+    reNonSpecialSpecials.ReplaceAll(str, "\\1");
+    reAccents.ReplaceAll(str, "\\1");
 }
 
 static wxString GetAllComments(const spContext& ctx)
 }
 
 static wxString GetAllComments(const spContext& ctx)
@@ -751,4 +1968,89 @@ static const char *GetCurrentTime(const char *timeFormat)
     return s_timeBuffer;
 }
 
     return s_timeBuffer;
 }
 
+static const wxString GetVersionString()
+{
+    wxString version = "$Revision$";
+    wxRegEx("^\\$Revision$$").ReplaceFirst(&version, "\\1");
+    return version;
+}
+
+/*
+   $Log$
+   Revision 1.14  2001/07/19 13:51:29  VZ
+   fixes to version string
+
+   Revision 1.13  2001/07/19 13:44:57  VZ
+   1. compilation fixes
+   2. don't quote special characters inside verbatim environment
+
+   Revision 1.12  2000/10/09 13:53:33  juliansmart
+
+   Doc corrections; added HelpGen project files
+
+   Revision 1.11  2000/07/15 19:50:42  cvsuser
+   merged 2.2 branch
+
+   Revision 1.10.2.2  2000/03/27 15:33:10  VZ
+   don't trasnform output dir name to lower case
+
+   Revision 1.10  2000/03/11 10:05:23  VS
+   now compiles with wxBase
+
+   Revision 1.9  2000/01/16 13:25:21  VS
+   compilation fixes (gcc)
+
+   Revision 1.8  1999/09/13 14:29:39  JS
+
+   Made HelpGen into a wxWin app (still uses command-line args); moved includes
+   into src for simplicity; added VC++ 5 project file
+
+   Revision 1.7  1999/02/21 22:32:32  VZ
+   1. more C++ parser fixes - now it almost parses wx/string.h
+    a) #if/#ifdef/#else (very) limited support
+    b) param type fix - now indirection chars are correctly handled
+    c) class/struct/union distinction
+    d) public/private fixes
+    e) Dump() function added - very useful for debugging
+
+   2. option to ignore parameter names during 'diff' (in fact, they're ignored
+      by default, and this option switches it on)
+
+   Revision 1.6  1999/02/20 23:00:26  VZ
+   1. new 'diff' mode which seems to work
+   2. output files are not overwritten in 'dmup' mode
+   3. fixes for better handling of const functions and operators
+    ----------------------------
+    revision 1.5
+    date: 1999/02/15 23:07:25;  author: VZ;  state: Exp;  lines: +106 -45
+    1. Parser improvements
+     a) const and virtual methods are parsed correctly (not static yet)
+     b) "const" which is part of the return type is not swallowed
+
+    2. HelpGen improvements: -o outputdir parameter added to the cmd line,
+       "//---------" kind comments discarded now.
+    ----------------------------
+    revision 1.4
+    date: 1999/01/13 14:23:31;  author: JS;  state: Exp;  lines: +4 -4
+
+    some tweaks to HelpGen
+    ----------------------------
+    revision 1.3
+    date: 1999/01/09 20:18:03;  author: JS;  state: Exp;  lines: +7 -2
+
+    HelpGen starting to compile with VC++
+    ----------------------------
+    revision 1.2
+    date: 1999/01/08 19:46:22;  author: VZ;  state: Exp;  lines: +208 -35
+
+    supports typedefs, generates "See also:" and adds "virtual " for virtual
+    functions
+    ----------------------------
+    revision 1.1
+    date: 1999/01/08 17:45:55;  author: VZ;  state: Exp;
+
+    HelpGen is a prototype of the tool for automatic generation of the .tex files
+    for wxWindows documentation from C++ headers
+*/
+
 /* vi: set tw=80 et ts=4 sw=4: */
 /* vi: set tw=80 et ts=4 sw=4: */