/////////////////////////////////////////////////////////////////////////////
// Name:        xmlparser.cpp
// Purpose:     Parser of the API/interface XML files
// Author:      Francesco Montorsi
// Created:     2008/03/17
// Copyright:   (c) 2008 Francesco Montorsi
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"


// for all others, include the necessary headers
#ifndef WX_PRECOMP
    #include "wx/crt.h"
#endif

#include "wx/xml/xml.h"
#include "wx/wfstream.h"
#include "wx/hashmap.h"
#include "wx/filename.h"
#include "xmlparser.h"
#include <errno.h>

#include <wx/arrimpl.cpp>
WX_DEFINE_OBJARRAY(wxTypeArray)
WX_DEFINE_OBJARRAY(wxArgumentTypeArray)
WX_DEFINE_OBJARRAY(wxMethodArray)
WX_DEFINE_OBJARRAY(wxClassArray)


#define PROGRESS_RATE             1000     // each PROGRESS_RATE nodes processed print a dot
#define ESTIMATED_NUM_CLASSES     600      // used by both wxXmlInterface-derived classes to prealloc mem


// defined in ifacecheck.cpp
extern bool g_verbose;

// global variable:
bool g_bLogEnabled = true;



// ----------------------------------------------------------------------------
// wxType
// ----------------------------------------------------------------------------

wxType wxEmptyType;

void wxType::SetTypeFromString(const wxString& t)
{
    /*
        TODO: optimize the following code writing a single function
              which works at char-level and does everything in a single pass
    */

    // clean the type string
    // ---------------------

    m_strType = t;

    // [] is the same as * for gccxml
    m_strType.Replace("[]", "*");
    m_strType.Replace("long int", "long");      // in wx typically we never write "long int", just "long"
    m_strType.Replace("long unsigned int", "unsigned long");
    m_strType.Replace("short unsigned int", "unsigned short");

    // make sure the * and & operator always use the same spacing rules
    // (to make sure GetAsString() output is always consistent)
    m_strType.Replace("*", "* ");
    m_strType.Replace("&", "& ");
    m_strType.Replace(" *", "*");
    m_strType.Replace(" &", "&");

    while (m_strType.Contains("  "))
        m_strType.Replace("  ", " ");       // do it once again

    m_strType.Replace(" ,", ",");

    // ADHOC-FIX
    m_strType.Replace("ExitCode", "void*");     // used in wxThread stuff

    m_strType = m_strType.Strip(wxString::both);



    // clean the type string (this time for the comparison)
    // ----------------------------------------------------

    m_strTypeClean = m_strType;     // begin with the already-cleaned string
    m_strTypeClean.Replace("const", "");
    m_strTypeClean.Replace("static", "");
    m_strTypeClean.Replace("*", "");
    m_strTypeClean.Replace("&", "");
    m_strTypeClean.Replace("[]", "");
    m_strTypeClean = m_strTypeClean.Strip(wxString::both);

    // to avoid false errors types like wxStandardPaths and wxStandardPathsBase
    // need to be considered as the same type
    if (m_strTypeClean.EndsWith("Base"))
        m_strTypeClean = m_strTypeClean.Left(m_strTypeClean.Len()-4);

    // remove the namespace from the types; there's no problem of conflicts
    // (except for templates) and this avoids tons of false warnings
    if (m_strTypeClean.Contains("::") && !m_strTypeClean.Contains("<"))
        m_strTypeClean = m_strTypeClean.Mid(m_strTypeClean.Find("::")+2);

    // ADHOC-FIX:
    m_strTypeClean.Replace("wxWindowID", "int");
}

bool wxType::IsOk() const
{
    // NB: m_strType can contain the :: operator; think to e.g. the
    //     "reverse_iterator_impl<wxString::const_iterator>" type
    //     It can also contain commas, * and & operators etc

    return !m_strTypeClean.IsEmpty();
}

bool wxType::operator==(const wxType& m) const
{
    // brain-dead comparison:

    if (m_strTypeClean == m.m_strTypeClean &&
        IsConst() == m.IsConst() &&
        IsStatic() == m.IsStatic() &&
        IsPointer() == m.IsPointer() &&
        IsReference() == m.IsReference())
        return true;

    if (g_verbose)
    {
        wxLogMessage("Type '%s' does not match type '%s'", m_strType, m.m_strType);
        wxLogMessage(" => TypeClean %s / %s;  IsConst %d / %d; IsStatic %d / %d; IsPointer %d / %d; IsReference %d / %d",
                   m_strTypeClean, m.m_strTypeClean, IsConst(), m.IsConst(),
                   IsStatic(), m.IsStatic(), IsPointer(), m.IsPointer(),
                   IsReference(), m.IsReference());
    }

    return false;
}


// ----------------------------------------------------------------------------
// wxArgumentType
// ----------------------------------------------------------------------------

void wxArgumentType::SetDefaultValue(const wxString& defval, const wxString& defvalForCmp)
{
    m_strDefaultValue = defval.Strip(wxString::both);
    m_strDefaultValueForCmp = defvalForCmp.IsEmpty() ?
                m_strDefaultValue : defvalForCmp.Strip(wxString::both);


    // clean the default argument strings
    // ----------------------------------

    // Note: we adjust the aesthetic form of the m_strDefaultValue string for the "modify mode"
    //       of ifacecheck: we may need to write it out in an interface header

    wxString *p = nullptr;
    for (int i=0; i<2; i++)     // to avoid copying&pasting the code!
    {
        if (i == 0) p = &m_strDefaultValue;
        if (i == 1) p = &m_strDefaultValueForCmp;

        if (*p == "0u" || *p == "0l") *p = "0";

        p->Replace("0x000000001", "1");
        p->Replace("\\000\\000\\000", "");    // fix for unicode strings:
        p->Replace("\\011", "\\t");
        p->Replace("e+0", "");
        p->Replace("2147483647", "__INT_MAX__");

        // ADHOC-FIX: for wxConv* default values
        p->Replace("wxConvAuto(wxFONTENCODING_DEFAULT)", "wxConvAuto()");
        p->Replace("wxGet_wxConvUTF8()", "wxConvUTF8");
        p->Replace("wxGet_wxConvLocal()", "wxConvLocal");
    }


    // clean ONLY the default argument string specific for comparison
    // --------------------------------------------------------------

    if (m_strDefaultValueForCmp.StartsWith("wxT(") &&
        m_strDefaultValueForCmp.EndsWith(")"))
    {
        // get rid of the wxT() part
        unsigned int len = m_strDefaultValueForCmp.Len();
        m_strDefaultValueForCmp = m_strDefaultValueForCmp.Mid(4,len-5);
    }

    // ADHOC-FIX:
    // doxygen likes to put wxDateTime:: in front of all wxDateTime enums;
    // fix this to avoid false positives
    m_strDefaultValueForCmp.Replace("wxDateTime::", "");
    m_strDefaultValueForCmp.Replace("wxStockGDI::", "");     // same story for some other classes
    m_strDefaultValueForCmp.Replace("wxHelpEvent::", "");    // same story for some other classes
    m_strDefaultValueForCmp.Replace("* GetColour(COLOUR_BLACK)", "*wxBLACK");

    // ADHOC-FIX:
    if (m_strDefaultValueForCmp.Contains("wxGetTranslation"))
        m_strDefaultValueForCmp = "_(TOFIX)";     // TODO: wxGetTranslation gives problems to gccxml
}

bool wxArgumentType::operator==(const wxArgumentType& m) const
{
    if ((const wxType&)(*this) != (const wxType&)m)
        return false;

    // check if the default values match
    // ---------------------------------


    // ADHOC-FIX:
    // default values for style attributes of wxWindow-derived classes in gccxml appear as raw
    // numbers; avoid false positives in this case!
    if (m_strArgName == m.m_strArgName && m_strArgName == "style" &&
        (m_strDefaultValueForCmp.IsNumber() || m.m_strDefaultValueForCmp.IsNumber()))
        return true;

    // fix for default values which were replaced by gcc-xml with their numeric values
    // (at this point we know that m_strTypeClean == m.m_strTypeClean):
    if (m_strTypeClean == "long" || m_strTypeClean == "int")
    {
        if ((m_strDefaultValueForCmp.IsNumber() && m.m_strDefaultValueForCmp.StartsWith("wx")) ||
            (m.m_strDefaultValueForCmp.IsNumber() && m_strDefaultValueForCmp.StartsWith("wx")))
        {
            if (g_verbose)
            {
                wxLogMessage("Supposing '%s'  default value to be the same of '%s'...",
                           m_strDefaultValueForCmp, m.m_strDefaultValueForCmp);
            }

            return true;
        }
    }
    else if (m_strTypeClean == "float" || m_strTypeClean == "double")
        // gccXML translates the default floating values in a hardly usable
        // format; e.g. 25.2 => 2.51999999999999992894572642398998141288757324219e+1
        // we avoid check on these...
        return true;

    if (m_strDefaultValueForCmp != m.m_strDefaultValueForCmp)
    {
        // maybe the default values are numbers.
        // in this case gccXML gives as default values things like '-0x0000001' instead of just '-1'.
        // To handle these cases, we try to convert the default value strings to numbers:
        long def1val, def2val;
        if (m_strDefaultValueForCmp.ToLong(&def1val, 0 /* auto-detect */) &&
            m.m_strDefaultValueForCmp.ToLong(&def2val, 0 /* auto-detect */))
        {
            if (def1val == def2val)
                return true;        // the default values match
        }

        if (g_verbose)
        {
            wxLogMessage("Argument type '%s = %s' has different default value from '%s = %s'",
                       m_strType, m_strDefaultValueForCmp, m.m_strType, m.m_strDefaultValueForCmp);
        }
        return false;
    }

    // we deliberately avoid checks on the argument name

    return true;
}


// ----------------------------------------------------------------------------
// wxMethod
// ----------------------------------------------------------------------------

bool wxMethod::IsOk() const
{
    // NOTE: m_retType can be a wxEmptyType, and means that this method
    //       is a ctor or a dtor.
    if (!m_retType.IsOk() && m_retType!=wxEmptyType) {
        wxLogError("'%s' method has invalid return type: %s", m_retType.GetAsString());
        return false;
    }

    if (m_strName.IsEmpty())
        return false;

    // a function can't be both const and static or virtual and static!
    if ((m_bConst && m_bStatic) || ((m_bVirtual || m_bPureVirtual) && m_bStatic)) {
        wxLogError("'%s' method can't be both const/static or virtual/static", m_strName);
        return false;
    }

    wxASSERT(!m_bPureVirtual || (m_bPureVirtual && m_bVirtual));

    for (unsigned int i=0; i<m_args.GetCount(); i++)
        if (!m_args[i].IsOk()) {
            wxLogError("'%s' method has invalid %d-th argument type: %s",
                     m_strName, i+1, m_args[i].GetAsString());
            return false;
        }

    // NB: the default value of the arguments can contain pretty much everything
    //     (think to e.g. wxPoint(3+4/2,0)   or   *wxBLACK   or  someClass<type>)
    //     so we don't do any test on their contents
    if (m_args.GetCount()>0)
    {
        bool previousArgHasDefault = m_args[0].HasDefaultValue();
        for (unsigned int i=1; i<m_args.GetCount(); i++)
        {
            if (previousArgHasDefault && !m_args[i].HasDefaultValue()) {
                wxLogError("'%s' method has %d-th argument which has no default value "
                         "(while the previous one had one!)",
                         m_strName, i+1);
                return false;
            }

            previousArgHasDefault = m_args[i].HasDefaultValue();
        }
    }

    return true;
}

bool wxMethod::MatchesExceptForAttributes(const wxMethod& m) const
{
    if (GetReturnType() != m.GetReturnType() ||
        GetName() != m.GetName())
    {
        if (g_verbose)
        {
            wxLogMessage("The method '%s' does not match method '%s'; different names/rettype", GetName(), m.GetName());
        }
        return false;
    }

    if (m_args.GetCount()!=m.m_args.GetCount()) {
        if (g_verbose)
        {
            wxLogMessage("Method '%s' has %d arguments while '%s' has %d arguments",
                       m_strName, m_args.GetCount(), m_strName, m.m_args.GetCount());
        }
        return false;
    }

    // compare argument types
    for (unsigned int i=0; i<m_args.GetCount(); i++)
        if (m_args[i] != m.m_args[i])
            return false;

    return true;
}

bool wxMethod::ActsAsDefaultCtor() const
{
    if (!IsCtor())
        return false;

    for (unsigned int i=0; i<m_args.GetCount(); i++)
        if (!m_args[i].HasDefaultValue())
            return false;

    return true;
}

bool wxMethod::operator==(const wxMethod& m) const
{
    // check attributes
    if (IsConst() != m.IsConst() ||
        IsStatic() != m.IsStatic() ||
        IsVirtual() != m.IsVirtual() ||
        IsPureVirtual() != m.IsPureVirtual() ||
        IsDeprecated() != m.IsDeprecated() ||
        GetAccessSpecifier() != m.GetAccessSpecifier())
    {
        if (g_verbose)
        {
            wxLogMessage("The method '%s' does not match method '%s'; different attributes", GetName(), m.GetName());
        }

        return false;
    }

    // check everything else
    return MatchesExceptForAttributes(m);
}

wxString wxMethod::GetAsString(bool bWithArgumentNames, bool bCleanDefaultValues,
                               bool bDeprecated, bool bAccessSpec) const
{
    wxString ret;

    // NOTE: for return and argument types, never use wxType::GetAsCleanString
    //       since in that way we'd miss important decorators like &,*,const etc

    if (m_retType!=wxEmptyType)
        ret += m_retType.GetAsString() + " ";
    //else; this is a ctor or dtor

    ret += m_strName + "(";

    for (unsigned int i=0; i<m_args.GetCount(); i++)
    {
        ret += m_args[i].GetAsString();

        const wxString& name = m_args[i].GetArgumentName();
        if (bWithArgumentNames && !name.IsEmpty())
            ret += " " + name;

        const wxString& def = bCleanDefaultValues ?
            m_args[i].GetDefaultCleanValue() : m_args[i].GetDefaultValue();
        if (!def.IsEmpty())
            ret += " = " + def;

        ret += ", ";
    }

    if (m_args.GetCount()>0)
        ret = ret.Left(ret.Len()-2);

    ret += ")";

    if (m_bConst)
        ret += " const";
    if (m_bStatic)
        ret = "static " + ret;
    if (m_bVirtual || m_bPureVirtual)
        ret = "virtual " + ret;
    if (m_bPureVirtual)
        ret += " = 0";
    if (m_bDeprecated && bDeprecated)
        ret += " [deprecated]";

    if (bAccessSpec)
    {
        switch (m_access)
        {
        case wxMAS_PUBLIC:
            ret += " [public]";
            break;
        case wxMAS_PROTECTED:
            ret += " [protected]";
            break;
        case wxMAS_PRIVATE:
            ret += " [private]";
            break;
        }
    }

    return ret;
}

void wxMethod::Dump(wxTextOutputStream& stream) const
{
    stream << "[" + m_retType.GetAsString() + "]";
    stream << "[" + m_strName + "]";

    for (unsigned int i=0; i<m_args.GetCount(); i++)
        stream << "[" + m_args[i].GetAsString() + " " + m_args[i].GetArgumentName() +
                  "=" + m_args[i].GetDefaultValue() + "]";

    if (IsConst())
        stream << " CONST";
    if (IsStatic())
        stream << " STATIC";
    if (IsVirtual())
        stream << " VIRTUAL";
    if (IsPureVirtual())
        stream << " PURE-VIRTUAL";
    if (IsDeprecated())
        stream << " DEPRECATED";

    // no final newline
}

// ----------------------------------------------------------------------------
// wxClass
// ----------------------------------------------------------------------------

wxString wxClass::GetNameWithoutTemplate() const
{
    // NB: I'm not sure this is the right terminology for this function!

    if (m_strName.Contains("<"))
        return m_strName.Left(m_strName.Find("<"));
    return m_strName;
}

bool wxClass::IsValidCtorForThisClass(const wxMethod& m) const
{
    // remember that e.g. the ctor for wxScopedCharBuffer<wchar_t> is
    // named wxScopedCharBuffer, without the <...> part!

    if (m.IsCtor() && m.GetName() == GetNameWithoutTemplate())
        return true;

    return false;
}

bool wxClass::IsValidDtorForThisClass(const wxMethod& m) const
{
    if (m.IsDtor() && m.GetName() == "~" + GetNameWithoutTemplate())
        return true;

    return false;
}

void wxClass::Dump(wxTextOutputStream& out) const
{
    out << m_strName + "\n";

    for (unsigned int i=0; i<m_methods.GetCount(); i++) {

        // dump all our methods
        out << "|- ";
        m_methods[i].Dump(out);
        out << "\n";
    }

    out << "\n";
}

bool wxClass::CheckConsistency() const
{
    for (unsigned int i=0; i<m_methods.GetCount(); i++)
        for (unsigned int j=0; j<m_methods.GetCount(); j++)
            if (i!=j && m_methods[i] == m_methods[j])
            {
                wxLogError("class %s has two methods with the same prototype: '%s'",
                         m_strName, m_methods[i].GetAsString());
                return false;

                // fix the problem?
                //((wxClass*)this)->m_methods.RemoveAt(j);
                //j--;
            }

    return true;
}

const wxMethod* wxClass::FindMethod(const wxMethod& m) const
{
    for (unsigned int i=0; i<m_methods.GetCount(); i++)
        if (m_methods[i] == m)
            return &m_methods[i];
    return nullptr;
}

const wxMethod* wxClass::RecursiveUpwardFindMethod(const wxMethod& m,
                                                   const wxXmlInterface* allclasses) const
{
    // first, search into *this
    const wxMethod* ret = FindMethod(m);
    if (ret)
        return ret;

    // then, search into its parents
    for (unsigned int i=0; i<m_parents.GetCount(); i++)
    {
        // ignore non-wx-classes parents
        // AD-HOC FIX: discard wxScrolledT_Helper parent as it always gives errors
        if (m_parents[i].StartsWith("wx") && m_parents[i] != "wxScrolledT_Helper")
        {
            const wxClass *parent = allclasses->FindClass(m_parents[i]);
            if (!parent) {
                wxLogError("Could not find parent '%s' of class '%s'...",
                         m_parents[i], GetName());
                return nullptr;
            }

            const wxMethod *parentMethod = parent->RecursiveUpwardFindMethod(m, allclasses);
            if (parentMethod)
                return parentMethod;
        }
    }

    // could not find anything even in parent classes...
    return nullptr;
}

wxMethodPtrArray wxClass::FindMethodsNamed(const wxString& name) const
{
    wxMethodPtrArray ret;

    for (unsigned int i=0; i<m_methods.GetCount(); i++)
        if (m_methods[i].GetName() == name)
            ret.Add(&m_methods[i]);

    return ret;
}


wxMethodPtrArray wxClass::RecursiveUpwardFindMethodsNamed(const wxString& name,
                                                          const wxXmlInterface* allclasses) const
{
    // first, search into *this
    wxMethodPtrArray ret = FindMethodsNamed(name);
    if (ret.GetCount()>0)
        return ret;         // stop here, don't look upward in the parents

    // then, search into parents of this class
    for (unsigned int i=0; i<m_parents.GetCount(); i++)
    {
        // AD-HOC FIX: discard wxScrolledT_Helper parent as it always gives errors
        if (m_parents[i].StartsWith("wx") && m_parents[i] != "wxScrolledT_Helper")
        {
            const wxClass *parent = allclasses->FindClass(m_parents[i]);
            if (!parent) {
                wxLogError("Could not find parent '%s' of class '%s'...",
                         m_parents[i], GetName());
                return wxMethodPtrArray();
            }

            wxMethodPtrArray temp = parent->RecursiveUpwardFindMethodsNamed(name, allclasses);
            WX_APPEND_ARRAY(ret, temp);
        }
    }

    return ret;
}



// ----------------------------------------------------------------------------
// wxXmlInterface
// ----------------------------------------------------------------------------

WX_DEFINE_SORTED_ARRAY(wxClass*, wxSortedClassArray);

int CompareWxClassObjects(wxClass *item1, wxClass *item2)
{
    // sort alphabetically
    return item1->GetName().Cmp(item2->GetName());
}

void wxXmlInterface::Dump(const wxString& filename)
{
    wxFFileOutputStream apioutput( filename );
    wxTextOutputStream apiout( apioutput );

    // dump the classes in alphabetical order
    wxSortedClassArray sorted(CompareWxClassObjects);
    sorted.Alloc(m_classes.GetCount());

    unsigned i;
    for (i=0; i<m_classes.GetCount(); i++)
        sorted.Add(&m_classes[i]);

    // now they have been sorted
    for (i=0; i<sorted.GetCount(); i++)
        sorted[i]->Dump(apiout);
}

bool wxXmlInterface::CheckConsistency() const
{
    // this check can be quite slow, so do it only for debug releases:
//#ifdef __WXDEBUG__
    for (unsigned int i=0; i<m_classes.GetCount(); i++)
    {
        if (!m_classes[i].CheckConsistency())
            return false;

        for (unsigned int j=0; j<m_classes.GetCount(); j++)
            if (i!=j && m_classes[i].GetName() == m_classes[j].GetName())
            {
                wxLogError("two classes have the same name: %s",
                         m_classes[i].GetName());
                return false;
            }
    }
//#endif

    return true;
}

wxClassPtrArray wxXmlInterface::FindClassesDefinedIn(const wxString& headerfile) const
{
    wxClassPtrArray ret;

    for (unsigned int i=0; i<m_classes.GetCount(); i++)
        if (m_classes[i].GetHeader() == headerfile)
            ret.Add(&m_classes[i]);

    return ret;
}


// ----------------------------------------------------------------------------
// wxXmlGccInterface helper declarations
// ----------------------------------------------------------------------------

// or-able flags for a toResolveTypeItem->attrib:
#define ATTRIB_CONST        1
#define ATTRIB_REFERENCE    2
#define ATTRIB_POINTER      4
#define ATTRIB_ARRAY        8

// it may sound strange but gccxml, in order to produce shorter ID names
// uses (after the underscore) characters in range 0-9 and a-z in the ID names;
// in order to be able to translate such strings into numbers using strtoul()
// we use as base 10 (possible digits) + 25 (possible characters) = 35
#define GCCXML_BASE         35

class toResolveTypeItem
{
public:
    toResolveTypeItem() { attribs=0; }
    toResolveTypeItem(unsigned int refID, unsigned int attribint)
        : ref(refID), attribs(attribint) {}

    unsigned long ref,       // the referenced type's ID
                  attribs;   // the attributes of this reference
};

#if 1

// for wxToResolveTypeHashMap, keys == gccXML IDs  and  values == toResolveTypeItem
WX_DECLARE_HASH_MAP( unsigned long, toResolveTypeItem,
                     wxIntegerHash, wxIntegerEqual,
                     wxToResolveTypeHashMap );

// for wxClassMemberIdHashMap, keys == gccXML IDs  and  values == wxClass which owns that member ID
WX_DECLARE_HASH_MAP( unsigned long, wxClass*,
                     wxIntegerHash, wxIntegerEqual,
                     wxClassMemberIdHashMap );

#else
#include <map>
typedef std::map<unsigned long, toResolveTypeItem> wxToResolveTypeHashMap;
#endif


// utility to parse gccXML ID values;
// this function is equivalent to wxString(str).Mid(1).ToULong(&id, GCCXML_BASE)
// but is a little bit faster
bool getID(unsigned long *id, const wxString& str)
{
    const wxStringCharType * const start = str.wx_str()+1;
    wxStringCharType *end;
#if wxUSE_UNICODE_WCHAR
    unsigned long val = wcstoul(start, &end, GCCXML_BASE);
#else
    unsigned long val = strtoul(start, &end, GCCXML_BASE);
#endif

    // return true only if scan was stopped by the terminating NUL and
    // if the string was not empty to start with and no under/overflow
    // occurred:
    if ( *end != '\0' || end == start || errno == ERANGE || errno == EINVAL )
        return false;

    *id = val;
    return true;
}

// utility specialized to parse efficiently the gccXML list of IDs which occur
// in nodes like <Class> ones... i.e. numeric values separated by " _" token
bool getMemberIDs(wxClassMemberIdHashMap* map, wxClass* p, const wxString& str)
{
    const wxStringCharType * const start = str.wx_str();
#if wxUSE_UNICODE_WCHAR
    size_t len = wcslen(start);
#else
    size_t len = strlen(start);
#endif

    if (len == 0 || start[0] != '_')
        return false;

    const wxStringCharType *curpos = start,
                           *end = start + len;
    wxStringCharType *nexttoken;

    while (curpos < end)
    {
        // curpos always points to the underscore of the next token to parse:
#if wxUSE_UNICODE_WCHAR
        unsigned long id = wcstoul(curpos+1, &nexttoken, GCCXML_BASE);
#else
        unsigned long id = strtoul(curpos+1, &nexttoken, GCCXML_BASE);
#endif
        if ( *nexttoken != ' ' || errno == ERANGE || errno == EINVAL )
            return false;

        // advance current position
        curpos = nexttoken + 1;

        // add this ID to the hashmap
        wxClassMemberIdHashMap::value_type v(id, p);
        map->insert(v);
    }

    return true;
}


// ----------------------------------------------------------------------------
// wxXmlGccInterface
// ----------------------------------------------------------------------------

bool wxXmlGccInterface::Parse(const wxString& filename)
{
    wxXmlDocument doc;
    wxXmlNode *child;
    int nodes = 0;

    wxLogMessage("Parsing %s...", filename);

    if (!doc.Load(filename)) {
        wxLogError("can't load %s", filename);
        return false;
    }

    // start processing the XML file
    if (doc.GetRoot()->GetName() != "GCC_XML") {
        wxLogError("invalid root node for %s", filename);
        return false;
    }

    wxString version = doc.GetRoot()->GetAttribute("cvs_revision");
    bool old = false;

#define MIN_REVISION  120

    if (!version.StartsWith("1."))
        old = true;
    if (!old)
    {
        unsigned long rev = 0;
        if (!version.Mid(2).ToULong(&rev))
            old = true;
        else
            if (rev < MIN_REVISION)
                old = true;
    }

    if (old)
    {
        wxLogError("The version of GCC-XML used for the creation of %s is too old; "
                 "the cvs_revision attribute of the root node reports '%s', "
                 "minimal required is 1.%d.", filename, version, MIN_REVISION);
        return false;
    }

    wxToResolveTypeHashMap toResolveTypes;
    wxClassMemberIdHashMap members;
    wxTypeIdHashMap types;
    wxTypeIdHashMap files;
    wxTypeIdHashMap typedefs;

    // prealloc quite a lot of memory!
    m_classes.Alloc(ESTIMATED_NUM_CLASSES);

    // build a list of wx classes and in general of all existent types
    child = doc.GetRoot()->GetChildren();
    while (child)
    {
        const wxString& n = child->GetName();

        unsigned long id = 0;
        if (!getID(&id, child->GetAttribute("id")) || (id == 0 && n != "File")) {

            // NOTE: <File> nodes can have an id == "f0"...

            wxLogError("Invalid id for node %s: %s", n, child->GetAttribute("id"));
            return false;
        }

        if (n == "Class")
        {
            wxString cname = child->GetAttribute("name");
            if (cname.IsEmpty()) {
                wxLogError("Invalid empty name for '%s' node", n);
                return false;
            }

            // only register wx classes (do remember also the IDs of their members)
            if (cname.StartsWith("wx"))
            {
                // NB: "file" attribute contains an ID value that we'll resolve later
                m_classes.Add(wxClass(cname, child->GetAttribute("file")));

                // the just-inserted class:
                wxClass *newClass = &m_classes.Last();

                // now get a list of the base classes:
                wxXmlNode *baseNode = child->GetChildren();
                while (baseNode)
                {
                    // for now we store as "parents" only the parent IDs...
                    // later we will resolve them into full class names
                    if (baseNode->GetName() == "Base")
                        newClass->AddParent(baseNode->GetAttribute("type"));

                    baseNode = baseNode->GetNext();
                }

                const wxString& ids = child->GetAttribute("members");
                if (ids.IsEmpty())
                {
                    if (child->GetAttribute("incomplete") != "1") {
                        wxLogError("Invalid member IDs for '%s' class node: %s",
                                cname, child->GetAttribute("id"));
                        return false;
                    }
                    //else: don't warn the user; it looks like "incomplete" classes
                    //      never have any member...
                }
                else
                {
                    // decode the non-empty list of IDs:
                    if (!getMemberIDs(&members, newClass, ids)) {
                        wxLogError("Invalid member IDs for '%s' class node: %s",
                                cname, child->GetAttribute("id"));
                        return false;
                    }
                }
            }

            // register this class also as possible return/argument type:
            types[id] = cname;
        }
        else if (n == "Typedef")
        {
            unsigned long typeId = 0;
            if (!getID(&typeId, child->GetAttribute("type"))) {
                wxLogError("Invalid type for node %s: %s", n, child->GetAttribute("type"));
                return false;
            }

            // this typedef node tell us that every type referenced with the
            // "typeId" ID should be called with another name:
            wxString name = child->GetAttribute("name");

            // save this typedef in a separate hashmap...
            typedefs[typeId] = name;

            types[id] = name;
        }
        else if (n == "PointerType" || n == "ReferenceType" ||
                 n == "CvQualifiedType" || n == "ArrayType")
        {
            unsigned long type = 0;
            if (!getID(&type, child->GetAttribute("type")) || type == 0) {
                wxLogError("Invalid type for node %s: %s", n, child->GetAttribute("type"));
                return false;
            }

            unsigned long attr = 0;
            if (n == "PointerType")
                attr = ATTRIB_POINTER;
            else if (n == "ReferenceType")
                attr = ATTRIB_REFERENCE;
            else if (n == "CvQualifiedType" && child->GetAttribute("const") == "1")
                attr = ATTRIB_CONST;
            else if (n == "ArrayType")
                attr = ATTRIB_ARRAY;

            // these nodes make reference to other types... we'll resolve them later
            toResolveTypes[id] = toResolveTypeItem(type, attr);
        }
        else if (n == "FunctionType" || n == "MethodType")
        {
            /*
                 TODO: parsing FunctionType and MethodType nodes is not as easy
                       as for other "simple" types.
            */

            wxString argstr;
            wxXmlNode *arg = child->GetChildren();
            while (arg)
            {
                if (arg->GetName() == "Argument")
                    argstr += arg->GetAttribute("type") + ", ";
                arg = arg->GetNext();
            }

            if (argstr.Len() > 0)
                argstr = argstr.Left(argstr.Len()-2);       // remove final comma

            // these nodes make reference to other types... we'll resolve them later
            //toResolveTypes[id] = toResolveTypeItem(ret, 0);
            //types[id] = child->GetAttribute("returns") + "(" + argstr + ")";

            types[id] = "TOFIX";   // typically this type will be "fixed" thanks
                                   // to a typedef later...
        }
        else if (n == "File")
        {
            if (!child->GetAttribute("id").StartsWith("f")) {
                wxLogError("Unexpected file ID: %s", child->GetAttribute("id"));
                return false;
            }

            // just ignore this node... all file IDs/names were already parsed
            files[id] = child->GetAttribute("name");
        }
        else
        {
            // we register everything else as a possible return/argument type:
            const wxString& name = child->GetAttribute("name");

            if (!name.IsEmpty())
            {
                //typeIds.Add(id);
                //typeNames.Add(name);
                types[id] = name;
            }
            else
            {
                // this may happen with unnamed structs/union, special ctors,
                // or other exotic things which we are not interested to, since
                // they're never used as return/argument types by wxWidgets methods

                if (g_verbose)
                {
                    wxLogWarning("Type node '%s' with ID '%s' does not have name attribute",
                               n, child->GetAttribute("id"));
                }

                types[id] = "TOFIX";
            }
        }

        child = child->GetNext();

        // give feedback to the user about the progress...
        if ((++nodes%PROGRESS_RATE)==0) ShowProgress();
    }

    // some nodes with IDs referenced by methods as return/argument types, do reference
    // in turn other nodes (see PointerType, ReferenceType and CvQualifierType above);
    // thus we need to resolve their name iteratively:
    while (toResolveTypes.size()>0)
    {
        if (g_verbose)
        {
            wxLogMessage("%d types were collected; %d types need yet to be resolved...",
                       types.size(), toResolveTypes.size());
        }

        for (wxToResolveTypeHashMap::iterator i = toResolveTypes.begin();
             i != toResolveTypes.end();)
        {
            unsigned long id = i->first;
            unsigned long referenced = i->second.ref;

            wxTypeIdHashMap::iterator primary = types.find(referenced);
            if (primary != types.end())
            {
                // this to-resolve-type references a "primary" type

                wxString newtype = primary->second;
                int attribs = i->second.attribs;

                // attribs may contain a combination of ATTRIB_* flags:
                if (attribs & ATTRIB_CONST)
                    newtype = "const " + newtype;
                if (attribs & ATTRIB_REFERENCE)
                    newtype = newtype + "&";
                if (attribs & ATTRIB_POINTER)
                    newtype = newtype + "*";
                if (attribs & ATTRIB_ARRAY)
                    newtype = newtype + "[]";

                // add the resolved type to the list of "primary" types
                if (newtype.Contains("TOFIX") && typedefs[id] != "")
                    types[id] = typedefs[id];       // better use a typedef for this type!
                else
                    types[id] = newtype;

                // this one has been resolved; erase it through its iterator!
                toResolveTypes.erase(i);

                // now iterator i is invalid; assign it again to the beginning
                i = toResolveTypes.begin();
            }
            else
            {
                // then search in the referenced types themselves:
                wxToResolveTypeHashMap::iterator idx2 = toResolveTypes.find(referenced);
                if (idx2 != toResolveTypes.end())
                {
                    // merge this to-resolve-type with the idx2->second type
                    i->second.ref = idx2->second.ref;
                    i->second.attribs |= idx2->second.attribs;

                    // this type will eventually be solved in the next while() iteration
                    i++;
                }
                else
                {
                    wxLogError("Cannot solve '%d' reference type!", referenced);
                    return false;
                }
            }
        }
    }

    // resolve header names
    unsigned i;
    for (i=0; i<m_classes.GetCount(); i++)
    {
        unsigned long fileID = 0;
        if (!getID(&fileID, m_classes[i].GetHeader()) || fileID == 0) {
            wxLogError("invalid header id: %s", m_classes[i].GetHeader());
            return false;
        }

        // search this file
        wxTypeIdHashMap::const_iterator idx = files.find(fileID);
        if (idx == files.end())
        {
            // this is an error!
            wxLogError("couldn't find file ID '%s'", m_classes[i].GetHeader());
        }
        else
            m_classes[i].SetHeader(idx->second);
    }

    // resolve parent names
    for (i=0; i<m_classes.GetCount(); i++)
    {
        for (unsigned int k=0; k<m_classes[i].GetParentCount(); k++)
        {
            unsigned long id;

            if (!getID(&id, m_classes[i].GetParent(k))) {
                wxLogError("invalid parent class ID for '%s'", m_classes[i].GetName());
                return false;
            }

            wxTypeIdHashMap::const_iterator idx = types.find(id);
            if (idx == types.end())
            {
                // this is an error!
                wxLogError("couldn't find parent class ID '%d'", id);
            }
            else
                // replace k-th parent with its true name:
                m_classes[i].SetParent(k, idx->second);
        }
    }

    // build the list of the wx methods
    child = doc.GetRoot()->GetChildren();
    while (child)
    {
        wxString n = child->GetName(), acc = child->GetAttribute("access");

        // only register public&protected methods
        if ((acc == "public" || acc == "protected") &&
            (n == "Method" || n == "Constructor" || n == "Destructor" || n == "OperatorMethod"))
        {
            unsigned long id = 0;
            if (!getID(&id, child->GetAttribute("id"))) {
                wxLogError("invalid ID for node '%s' with ID '%s'", n, child->GetAttribute("id"));
                return false;
            }

            wxClassMemberIdHashMap::const_iterator it = members.find(id);
            if (it != members.end())
            {
                wxClass *p = it->second;

                // this <Method> node is a method of the i-th class!
                wxMethod newfunc;
                if (!ParseMethod(child, types, newfunc)) {
                    wxLogError("The method '%s' could not be added to class '%s'",
                             child->GetAttribute("demangled"), p->GetName());
                    return false;
                }

                // do some additional check that we can do only here:

                if (newfunc.IsCtor() && !p->IsValidCtorForThisClass(newfunc)) {
                    wxLogError("The method '%s' does not seem to be a ctor for '%s'",
                             newfunc.GetName(), p->GetName());
                    return false;
                }
                if (newfunc.IsDtor() && !p->IsValidDtorForThisClass(newfunc)) {
                    wxLogError("The method '%s' does not seem to be a dtor for '%s'",
                             newfunc.GetName(), p->GetName());
                    return false;
                }

                p->AddMethod(newfunc);
            }
        }

        child = child->GetNext();

        // give feedback to the user about the progress...
        if ((++nodes%PROGRESS_RATE)==0) ShowProgress();
    }

    if (!CheckConsistency())
        return false;       // the check failed

    return true;
}

bool wxXmlGccInterface::ParseMethod(const wxXmlNode *p,
                                    const wxTypeIdHashMap& types,
                                    wxMethod& m)
{
    // get the real name
    wxString name = p->GetAttribute("name").Strip(wxString::both);
    if (p->GetName() == "Destructor")
        name = "~" + name;
    else if (p->GetName() == "OperatorMethod")
        name = "operator" + name;

    // resolve return type
    wxType ret;
    unsigned long retid = 0;
    if (!getID(&retid, p->GetAttribute("returns")) || retid == 0)
    {
        if (p->GetName() != "Destructor" && p->GetName() != "Constructor") {
            wxLogError("Empty return ID for method '%s', with ID '%s'",
                     name, p->GetAttribute("id"));
            return false;
        }
    }
    else
    {
        wxTypeIdHashMap::const_iterator retidx = types.find(retid);
        if (retidx == types.end()) {
            wxLogError("Could not find return type ID '%s'", retid);
            return false;
        }

        ret = wxType(retidx->second);
        if (!ret.IsOk()) {
            wxLogError("Invalid return type '%s' for method '%s', with ID '%s'",
                     retidx->second, name, p->GetAttribute("id"));
            return false;
        }
    }

    // resolve argument types
    wxArgumentTypeArray argtypes;
    wxXmlNode *arg = p->GetChildren();
    while (arg)
    {
        if (arg->GetName() == "Argument")
        {
            unsigned long id = 0;
            if (!getID(&id, arg->GetAttribute("type")) || id == 0) {
                wxLogError("Invalid argument type ID '%s' for method '%s' with ID %s",
                         arg->GetAttribute("type"), name, p->GetAttribute("id"));
                return false;
            }

            wxTypeIdHashMap::const_iterator idx = types.find(id);
            if (idx == types.end()) {
                wxLogError("Could not find argument type ID '%s'", id);
                return false;
            }

            argtypes.Add(wxArgumentType(idx->second,
                                        arg->GetAttribute("default"),
                                        arg->GetAttribute("name")));
        }

        arg = arg->GetNext();
    }

    m.SetReturnType(ret);
    m.SetName(name);
    m.SetArgumentTypes(argtypes);
    m.SetConst(p->GetAttribute("const") == "1");
    m.SetStatic(p->GetAttribute("static") == "1");

    // NOTE: gccxml is smart enough to mark as virtual those functions
    //       which are declared virtual in base classes but don't have
    //       the "virtual" keyword explicitly indicated in the derived
    //       classes... so we don't need any further logic for virtuals

    m.SetVirtual(p->GetAttribute("virtual") == "1");
    m.SetPureVirtual(p->GetAttribute("pure_virtual") == "1");
    m.SetDeprecated(p->GetAttribute("attributes") == "deprecated");

    // decode access specifier
    if (p->GetAttribute("access") == "public")
        m.SetAccessSpecifier(wxMAS_PUBLIC);
    else if (p->GetAttribute("access") == "protected")
        m.SetAccessSpecifier(wxMAS_PROTECTED);
    else if (p->GetAttribute("access") == "private")
        m.SetAccessSpecifier(wxMAS_PRIVATE);

    if (!m.IsOk()) {
        wxLogError("The prototype '%s' is not valid!", m.GetAsString());
        return false;
    }

    return true;
}



// ----------------------------------------------------------------------------
// wxXmlDoxygenInterface global helpers
// ----------------------------------------------------------------------------

static wxString GetTextFromChildren(const wxXmlNode *n)
{
    wxString text;

    // consider the tree
    //
    //  <a><b>this</b> is a <b>string</b></a>
    //
    // <a>
    // |- <b>
    // |   |- this
    // |- is a
    // |- <b>
    //     |- string
    //
    // unlike wxXmlNode::GetNodeContent() which would return " is a "
    // this function returns "this is a string"

    wxXmlNode *ref = n->GetChildren();
    while (ref) {
        if (ref->GetType() == wxXML_ELEMENT_NODE)
            text += ref->GetNodeContent();
        else if (ref->GetType() == wxXML_TEXT_NODE)
            text += ref->GetContent();
        else
            wxLogWarning("Unexpected node type while getting text from '%s' node", n->GetName());

        ref = ref->GetNext();
    }

    return text;
}

static bool HasTextNodeContaining(const wxXmlNode *parent, const wxString& name)
{
    if (!parent)
        return false;

    wxXmlNode *p = parent->GetChildren();
    while (p)
    {
        switch (p->GetType())
        {
            case wxXML_TEXT_NODE:
                if (p->GetContent() == name)
                    return true;
                break;

            case wxXML_ELEMENT_NODE:
                // recurse into this node...
                if (HasTextNodeContaining(p, name))
                    return true;
                break;

            default:
                // skip it
                break;
        }

        p = p->GetNext();
    }

    return false;
}

static const wxXmlNode* FindNodeNamed(const wxXmlNode* parent, const wxString& name)
{
    if (!parent)
        return nullptr;

    const wxXmlNode *p = parent->GetChildren();
    while (p)
    {
        if (p->GetName() == name)
            return p;       // found!

        // search recursively in the children of this node
        const wxXmlNode *ret = FindNodeNamed(p, name);
        if (ret)
            return ret;

        p = p->GetNext();
    }

    return nullptr;
}

int GetAvailabilityFor(const wxXmlNode *node)
{
    // identify <onlyfor> custom XML tags
    const wxXmlNode* onlyfor = FindNodeNamed(node, "onlyfor");
    if (!onlyfor)
        return wxPORT_UNKNOWN;

    wxArrayString ports = wxSplit(onlyfor->GetNodeContent(), ',');
    int nAvail = wxPORT_UNKNOWN;
    for (unsigned int i=0; i < ports.GetCount(); i++)
    {
        if (!ports[i].StartsWith("wx")) {
            wxLogError("unexpected port ID '%s'", ports[i]);
            return false;
        }

        nAvail |= wxPlatformInfo::GetPortId(ports[i].Mid(2));
    }

    return nAvail;
}


// ----------------------------------------------------------------------------
// wxXmlDoxygenInterface
// ----------------------------------------------------------------------------

bool wxXmlDoxygenInterface::Parse(const wxString& filename)
{
    wxXmlDocument index;
    wxXmlNode *compound;

    wxLogMessage("Parsing %s...", filename);

    if (!index.Load(filename)) {
        wxLogError("can't load %s", filename);
        return false;
    }

    // start processing the index:
    if (index.GetRoot()->GetName() != "doxygenindex") {
        wxLogError("invalid root node for %s", filename);
        return false;
    }

    /*
        NB: we may need in future to do a version-check here if the
            format of the XML generated by doxygen changes.
            For now (doxygen version 1.5.5), this check is not required
            since AFAIK the XML format never changed since it was introduced.
    */

    m_classes.Alloc(ESTIMATED_NUM_CLASSES);

    // process files referenced by this index file
    compound = index.GetRoot()->GetChildren();
    while (compound)
    {
        if (compound->GetName() == "compound" &&
            compound->GetAttribute("kind") == "class")
        {
            wxString refid = compound->GetAttribute("refid");

            wxFileName fn(filename);
            if (!ParseCompoundDefinition(fn.GetPath(wxPATH_GET_SEPARATOR) + refid + ".xml"))
                return false;
        }

        compound = compound->GetNext();
    }
    //wxPrint("\n");

    if (!CheckConsistency())
        return false;       // the check failed

    return true;
}

bool wxXmlDoxygenInterface::ParseCompoundDefinition(const wxString& filename)
{
    wxClassMemberIdHashMap parents;
    wxXmlDocument doc;
    wxXmlNode *child;
    int nodes = 0;

    if (g_verbose)
    {
        wxLogMessage("Parsing %s...", filename);
    }

    if (!doc.Load(filename)) {
        wxLogError("can't load %s", filename);
        return false;
    }

    // start processing this compound definition XML
    if (doc.GetRoot()->GetName() != "doxygen") {
        wxLogError("invalid root node for %s", filename);
        return false;
    }

    // build a list of wx classes
    child = doc.GetRoot()->GetChildren();
    while (child)
    {
        if (child->GetName() == "compounddef" &&
            child->GetAttribute("kind") == "class")
        {
            // parse this class
            wxClass klass;
            wxString absoluteFile, header;

            wxXmlNode *subchild = child->GetChildren();
            while (subchild)
            {
                // NOTE: when documenting functions using the //@{ and //@}
                //       tags to create function groups, doxygen puts the
                //       contained methods into a "user-defined" section
                //       so we _must_ use the "prot" attribute to distinguish
                //       public/protected methods from private ones and cannot
                //       rely on the kind="public" attribute of <sectiondef>
                if (subchild->GetName() == "sectiondef")
                {
                    wxXmlNode *membernode = subchild->GetChildren();
                    while (membernode)
                    {
                        const wxString& accessSpec = membernode->GetAttribute("prot");

                        // parse only public&protected functions:
                        if (membernode->GetName() == "memberdef" &&
                            membernode->GetAttribute("kind") == "function" &&
                            (accessSpec == "public" || accessSpec == "protected"))
                        {
                            wxMethod m;
                            if (!ParseMethod(membernode, m, header)) {
                                wxLogError("The method '%s' could not be added to class '%s'",
                                           m.GetName(), klass.GetName());
                                return false;
                            }

                            if (accessSpec == "public")
                                m.SetAccessSpecifier(wxMAS_PUBLIC);
                            else if (accessSpec == "protected")
                                m.SetAccessSpecifier(wxMAS_PROTECTED);
                            else if (accessSpec == "private")
                                m.SetAccessSpecifier(wxMAS_PRIVATE);

                            if (absoluteFile.IsEmpty())
                                absoluteFile = header;
                            else if (header != absoluteFile)
                            {
                                wxLogError("Found inconsistency in the XML file '%s': "
                                           "the method '%s' is documented in the "
                                           "file '%s' but the other methods of the same "
                                           "class are documented in the file '%s'",
                                            filename, m.GetName(), header, absoluteFile);
                                return false;
                            }

                            klass.AddMethod(m);
                        }

                        membernode = membernode->GetNext();
                    }

                    // all methods of this class were taken from the header "absoluteFile":
                    klass.SetHeader(absoluteFile);
                }
                else if (subchild->GetName() == "compoundname")
                {
                    klass.SetName(subchild->GetNodeContent());
                }
                /*else if (subchild->GetName() == "includes")
                {
                    // NOTE: we'll get the header from the <location> tags
                    //       scattered inside <memberdef> tags instead of
                    //       this <includes> tag since it does not contain
                    //       the absolute path of the header

                    klass.SetHeader(subchild->GetNodeContent());
                }*/
                else if (subchild->GetName() == "detaileddescription")
                {
                    // identify <onlyfor> custom XML tags
                    klass.SetAvailability(GetAvailabilityFor(subchild));
                }
                else if (subchild->GetName() == "basecompoundref")
                {
                    // add the name of this parent to the list of klass' parents
                    klass.AddParent(subchild->GetNodeContent());
                }

                subchild = subchild->GetNext();
            }

            // add a new class
            if (klass.IsOk())
            {
                m_classes.Add(klass);
            }
            else if (g_verbose)
            {
                wxLogWarning("discarding class '%s' with %d methods...",
                           klass.GetName(), klass.GetMethodCount());
            }
        }

        child = child->GetNext();

        // give feedback to the user about the progress...
        if ((++nodes%PROGRESS_RATE)==0) ShowProgress();
    }

    return true;
}

bool wxXmlDoxygenInterface::ParseMethod(const wxXmlNode* p, wxMethod& m, wxString& header)
{
    wxArgumentTypeArray args;
    long line;

    wxXmlNode *child = p->GetChildren();
    while (child)
    {
        if (child->GetName() == "name")
            m.SetName(child->GetNodeContent());
        else if (child->GetName() == "type")
            m.SetReturnType(wxType(GetTextFromChildren(child)));
        else if (child->GetName() == "param")
        {
            wxString typestr, namestr, defstr, arrstr;
            wxXmlNode *n = child->GetChildren();
            while (n)
            {
                if (n->GetName() == "type")
                    // if the <type> node has children, they should be all TEXT and <ref> nodes
                    // and we need to take the text they contain, in the order they appear
                    typestr = GetTextFromChildren(n);
                else if (n->GetName() == "declname")
                    namestr = GetTextFromChildren(n);
                else if (n->GetName() == "defval")
                    defstr = GetTextFromChildren(n).Strip(wxString::both);
                else if (n->GetName() == "array")
                    arrstr = GetTextFromChildren(n);

                n = n->GetNext();
            }

            if (typestr.IsEmpty()) {
                wxLogError("cannot find type node for a param in method '%s'", m.GetName());
                return false;
            }

            wxArgumentType newarg(typestr + arrstr, defstr, namestr);

            // can we use preprocessor output to transform the default value
            // into the same form which gets processed by wxXmlGccInterface?
            wxStringHashMap::const_iterator it = m_preproc.find(defstr);
            if (it != m_preproc.end())
                newarg.SetDefaultValue(defstr, it->second);

            args.Add(newarg);
        }
        else if (child->GetName() == "location")
        {
            line = -1;
            if (child->GetAttribute("line").ToLong(&line))
                m.SetLocation((int)line);
            header = child->GetAttribute("file");
        }
        else if (child->GetName() == "detaileddescription")
        {
            // when a method has a @deprecated tag inside its description,
            // Doxygen outputs somewhere nested inside <detaileddescription>
            // a <xreftitle>Deprecated</xreftitle> tag.
            m.SetDeprecated(HasTextNodeContaining(child, "Deprecated"));

            // identify <onlyfor> custom XML tags
            m.SetAvailability(GetAvailabilityFor(child));
        }

        child = child->GetNext();
    }

    m.SetArgumentTypes(args);
    m.SetConst(p->GetAttribute("const")=="yes");
    m.SetStatic(p->GetAttribute("static")=="yes");

    // NOTE: Doxygen is smart enough to mark as virtual those functions
    //       which are declared virtual in base classes but don't have
    //       the "virtual" keyword explicitly indicated in the derived
    //       classes... so we don't need any further logic for virtuals

    m.SetVirtual(p->GetAttribute("virt")=="virtual");
    m.SetPureVirtual(p->GetAttribute("virt")=="pure-virtual");

    if (!m.IsOk()) {
        wxLogError("The prototype '%s' is not valid!", m.GetAsString());
        return false;
    }

    return true;
}
