e2d1c0e72b
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@56718 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
1671 lines
54 KiB
C++
1671 lines
54 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: xmlparser.cpp
|
|
// Purpose: Parser of the API/interface XML files
|
|
// Author: Francesco Montorsi
|
|
// Created: 2008/03/17
|
|
// RCS-ID: $Id$
|
|
// Copyright: (c) 2008 Francesco Montorsi
|
|
// Licence: wxWindows licence
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// For compilers that support precompilation, includes "wx/wx.h".
|
|
#include "wx/wxprec.h"
|
|
|
|
#ifdef __BORLANDC__
|
|
#pragma hdrstop
|
|
#endif
|
|
|
|
// 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");
|
|
|
|
// 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("_wxArraywxArrayStringBase", "wxString");
|
|
|
|
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;
|
|
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 = "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 wxWritableCharTypeBuffer<wchar_t> is
|
|
// named wxWritableCharTypeBuffer, 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 NULL;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
const wxMethod *parentMethod = parent->RecursiveUpwardFindMethod(m, allclasses);
|
|
if (parentMethod)
|
|
return parentMethod;
|
|
}
|
|
}
|
|
|
|
// could not find anything even in parent classes...
|
|
return NULL;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
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());
|
|
for (unsigned int i=0; i<m_classes.GetCount(); i++)
|
|
sorted.Add(&m_classes[i]);
|
|
|
|
// now they have been sorted
|
|
for (unsigned int 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 separed 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
|
|
for (unsigned int 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 (unsigned int 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 explicitely 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 NULL;
|
|
|
|
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 NULL;
|
|
}
|
|
|
|
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)
|
|
{
|
|
wxString kind = subchild->GetAttribute("kind");
|
|
|
|
// parse only public&protected functions:
|
|
if (subchild->GetName() == "sectiondef" &&
|
|
(kind == "public-func" || kind == "protected-func"))
|
|
{
|
|
|
|
wxXmlNode *membernode = subchild->GetChildren();
|
|
while (membernode)
|
|
{
|
|
if (membernode->GetName() == "memberdef" &&
|
|
membernode->GetAttribute("kind") == "function")
|
|
{
|
|
|
|
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 (kind == "public-func")
|
|
m.SetAccessSpecifier(wxMAS_PUBLIC);
|
|
else if (kind == "protected-func")
|
|
m.SetAccessSpecifier(wxMAS_PROTECTED);
|
|
else if (kind == "private-func")
|
|
m.SetAccessSpecifier(wxMAS_PRIVATE);
|
|
|
|
if (absoluteFile.IsEmpty())
|
|
absoluteFile = header;
|
|
else if (header != absoluteFile)
|
|
{
|
|
wxLogError("The method '%s' is documented in a different "
|
|
"file from others (which belong to '%s') ?",
|
|
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 explicitely 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;
|
|
}
|