1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: Parser of the API/interface XML files
4 // Author: Francesco Montorsi
6 // Copyright: (c) 2008 Francesco Montorsi
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
10 // For compilers that support precompilation, includes "wx/wx.h".
11 #include "wx/wxprec.h"
17 // for all others, include the necessary headers
22 #include "wx/xml/xml.h"
23 #include "wx/wfstream.h"
24 #include "wx/hashmap.h"
25 #include "wx/filename.h"
26 #include "xmlparser.h"
29 #include <wx/arrimpl.cpp>
30 WX_DEFINE_OBJARRAY(wxTypeArray
)
31 WX_DEFINE_OBJARRAY(wxArgumentTypeArray
)
32 WX_DEFINE_OBJARRAY(wxMethodArray
)
33 WX_DEFINE_OBJARRAY(wxClassArray
)
36 #define PROGRESS_RATE 1000 // each PROGRESS_RATE nodes processed print a dot
37 #define ESTIMATED_NUM_CLASSES 600 // used by both wxXmlInterface-derived classes to prealloc mem
40 // defined in ifacecheck.cpp
41 extern bool g_verbose
;
44 bool g_bLogEnabled
= true;
48 // ----------------------------------------------------------------------------
50 // ----------------------------------------------------------------------------
54 void wxType::SetTypeFromString(const wxString
& t
)
57 TODO: optimize the following code writing a single function
58 which works at char-level and does everything in a single pass
61 // clean the type string
62 // ---------------------
66 // [] is the same as * for gccxml
67 m_strType
.Replace("[]", "*");
68 m_strType
.Replace("long int", "long"); // in wx typically we never write "long int", just "long"
69 m_strType
.Replace("long unsigned int", "unsigned long");
70 m_strType
.Replace("short unsigned int", "unsigned short");
72 // make sure the * and & operator always use the same spacing rules
73 // (to make sure GetAsString() output is always consistent)
74 m_strType
.Replace("*", "* ");
75 m_strType
.Replace("&", "& ");
76 m_strType
.Replace(" *", "*");
77 m_strType
.Replace(" &", "&");
79 while (m_strType
.Contains(" "))
80 m_strType
.Replace(" ", " "); // do it once again
82 m_strType
.Replace(" ,", ",");
85 m_strType
.Replace("_wxArraywxArrayStringBase", "wxString");
86 m_strType
.Replace("ExitCode", "void*"); // used in wxThread stuff
88 m_strType
= m_strType
.Strip(wxString::both
);
92 // clean the type string (this time for the comparison)
93 // ----------------------------------------------------
95 m_strTypeClean
= m_strType
; // begin with the already-cleaned string
96 m_strTypeClean
.Replace("const", "");
97 m_strTypeClean
.Replace("static", "");
98 m_strTypeClean
.Replace("*", "");
99 m_strTypeClean
.Replace("&", "");
100 m_strTypeClean
.Replace("[]", "");
101 m_strTypeClean
= m_strTypeClean
.Strip(wxString::both
);
103 // to avoid false errors types like wxStandardPaths and wxStandardPathsBase
104 // need to be considered as the same type
105 if (m_strTypeClean
.EndsWith("Base"))
106 m_strTypeClean
= m_strTypeClean
.Left(m_strTypeClean
.Len()-4);
108 // remove the namespace from the types; there's no problem of conflicts
109 // (except for templates) and this avoids tons of false warnings
110 if (m_strTypeClean
.Contains("::") && !m_strTypeClean
.Contains("<"))
111 m_strTypeClean
= m_strTypeClean
.Mid(m_strTypeClean
.Find("::")+2);
114 m_strTypeClean
.Replace("wxWindowID", "int");
117 bool wxType::IsOk() const
119 // NB: m_strType can contain the :: operator; think to e.g. the
120 // "reverse_iterator_impl<wxString::const_iterator>" type
121 // It can also contain commas, * and & operators etc
123 return !m_strTypeClean
.IsEmpty();
126 bool wxType::operator==(const wxType
& m
) const
128 // brain-dead comparison:
130 if (m_strTypeClean
== m
.m_strTypeClean
&&
131 IsConst() == m
.IsConst() &&
132 IsStatic() == m
.IsStatic() &&
133 IsPointer() == m
.IsPointer() &&
134 IsReference() == m
.IsReference())
139 wxLogMessage("Type '%s' does not match type '%s'", m_strType
, m
.m_strType
);
140 wxLogMessage(" => TypeClean %s / %s; IsConst %d / %d; IsStatic %d / %d; IsPointer %d / %d; IsReference %d / %d",
141 m_strTypeClean
, m
.m_strTypeClean
, IsConst(), m
.IsConst(),
142 IsStatic(), m
.IsStatic(), IsPointer(), m
.IsPointer(),
143 IsReference(), m
.IsReference());
150 // ----------------------------------------------------------------------------
152 // ----------------------------------------------------------------------------
154 void wxArgumentType::SetDefaultValue(const wxString
& defval
, const wxString
& defvalForCmp
)
156 m_strDefaultValue
= defval
.Strip(wxString::both
);
157 m_strDefaultValueForCmp
= defvalForCmp
.IsEmpty() ?
158 m_strDefaultValue
: defvalForCmp
.Strip(wxString::both
);
161 // clean the default argument strings
162 // ----------------------------------
164 // Note: we adjust the aesthetic form of the m_strDefaultValue string for the "modify mode"
165 // of ifacecheck: we may need to write it out in an interface header
168 for (int i
=0; i
<2; i
++) // to avoid copying&pasting the code!
170 if (i
== 0) p
= &m_strDefaultValue
;
171 if (i
== 1) p
= &m_strDefaultValueForCmp
;
173 if (*p
== "0u" || *p
== "0l") *p
= "0";
175 p
->Replace("0x000000001", "1");
176 p
->Replace("\\000\\000\\000", ""); // fix for unicode strings:
177 p
->Replace("\\011", "\\t");
178 p
->Replace("e+0", "");
179 p
->Replace("2147483647", "__INT_MAX__");
181 // ADHOC-FIX: for wxConv* default values
182 p
->Replace("wxConvAuto(wxFONTENCODING_DEFAULT)", "wxConvAuto()");
183 p
->Replace("wxGet_wxConvUTF8()", "wxConvUTF8");
184 p
->Replace("wxGet_wxConvLocal()", "wxConvLocal");
188 // clean ONLY the default argument string specific for comparison
189 // --------------------------------------------------------------
191 if (m_strDefaultValueForCmp
.StartsWith("wxT(") &&
192 m_strDefaultValueForCmp
.EndsWith(")"))
194 // get rid of the wxT() part
195 unsigned int len
= m_strDefaultValueForCmp
.Len();
196 m_strDefaultValueForCmp
= m_strDefaultValueForCmp
.Mid(4,len
-5);
200 // doxygen likes to put wxDateTime:: in front of all wxDateTime enums;
201 // fix this to avoid false positives
202 m_strDefaultValueForCmp
.Replace("wxDateTime::", "");
203 m_strDefaultValueForCmp
.Replace("wxStockGDI::", ""); // same story for some other classes
204 m_strDefaultValueForCmp
.Replace("wxHelpEvent::", ""); // same story for some other classes
205 m_strDefaultValueForCmp
.Replace("* GetColour(COLOUR_BLACK)", "*wxBLACK");
208 if (m_strDefaultValueForCmp
.Contains("wxGetTranslation"))
209 m_strDefaultValueForCmp
= "_(TOFIX)"; // TODO: wxGetTranslation gives problems to gccxml
212 bool wxArgumentType::operator==(const wxArgumentType
& m
) const
214 if ((const wxType
&)(*this) != (const wxType
&)m
)
217 // check if the default values match
218 // ---------------------------------
222 // default values for style attributes of wxWindow-derived classes in gccxml appear as raw
223 // numbers; avoid false positives in this case!
224 if (m_strArgName
== m
.m_strArgName
&& m_strArgName
== "style" &&
225 (m_strDefaultValueForCmp
.IsNumber() || m
.m_strDefaultValueForCmp
.IsNumber()))
228 // fix for default values which were replaced by gcc-xml with their numeric values
229 // (at this point we know that m_strTypeClean == m.m_strTypeClean):
230 if (m_strTypeClean
== "long" || m_strTypeClean
== "int")
232 if ((m_strDefaultValueForCmp
.IsNumber() && m
.m_strDefaultValueForCmp
.StartsWith("wx")) ||
233 (m
.m_strDefaultValueForCmp
.IsNumber() && m_strDefaultValueForCmp
.StartsWith("wx")))
237 wxLogMessage("Supposing '%s' default value to be the same of '%s'...",
238 m_strDefaultValueForCmp
, m
.m_strDefaultValueForCmp
);
244 else if (m_strTypeClean
== "float" || m_strTypeClean
== "double")
245 // gccXML translates the default floating values in a hardly usable
246 // format; e.g. 25.2 => 2.51999999999999992894572642398998141288757324219e+1
247 // we avoid check on these...
250 if (m_strDefaultValueForCmp
!= m
.m_strDefaultValueForCmp
)
252 // maybe the default values are numbers.
253 // in this case gccXML gives as default values things like '-0x0000001' instead of just '-1'.
254 // To handle these cases, we try to convert the default value strings to numbers:
255 long def1val
, def2val
;
256 if (m_strDefaultValueForCmp
.ToLong(&def1val
, 0 /* auto-detect */) &&
257 m
.m_strDefaultValueForCmp
.ToLong(&def2val
, 0 /* auto-detect */))
259 if (def1val
== def2val
)
260 return true; // the default values match
265 wxLogMessage("Argument type '%s = %s' has different default value from '%s = %s'",
266 m_strType
, m_strDefaultValueForCmp
, m
.m_strType
, m
.m_strDefaultValueForCmp
);
271 // we deliberately avoid checks on the argument name
277 // ----------------------------------------------------------------------------
279 // ----------------------------------------------------------------------------
281 bool wxMethod::IsOk() const
283 // NOTE: m_retType can be a wxEmptyType, and means that this method
284 // is a ctor or a dtor.
285 if (!m_retType
.IsOk() && m_retType
!=wxEmptyType
) {
286 wxLogError("'%s' method has invalid return type: %s", m_retType
.GetAsString());
290 if (m_strName
.IsEmpty())
293 // a function can't be both const and static or virtual and static!
294 if ((m_bConst
&& m_bStatic
) || ((m_bVirtual
|| m_bPureVirtual
) && m_bStatic
)) {
295 wxLogError("'%s' method can't be both const/static or virtual/static", m_strName
);
299 wxASSERT(!m_bPureVirtual
|| (m_bPureVirtual
&& m_bVirtual
));
301 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
302 if (!m_args
[i
].IsOk()) {
303 wxLogError("'%s' method has invalid %d-th argument type: %s",
304 m_strName
, i
+1, m_args
[i
].GetAsString());
308 // NB: the default value of the arguments can contain pretty much everything
309 // (think to e.g. wxPoint(3+4/2,0) or *wxBLACK or someClass<type>)
310 // so we don't do any test on their contents
311 if (m_args
.GetCount()>0)
313 bool previousArgHasDefault
= m_args
[0].HasDefaultValue();
314 for (unsigned int i
=1; i
<m_args
.GetCount(); i
++)
316 if (previousArgHasDefault
&& !m_args
[i
].HasDefaultValue()) {
317 wxLogError("'%s' method has %d-th argument which has no default value "
318 "(while the previous one had one!)",
323 previousArgHasDefault
= m_args
[i
].HasDefaultValue();
330 bool wxMethod::MatchesExceptForAttributes(const wxMethod
& m
) const
332 if (GetReturnType() != m
.GetReturnType() ||
333 GetName() != m
.GetName())
337 wxLogMessage("The method '%s' does not match method '%s'; different names/rettype", GetName(), m
.GetName());
342 if (m_args
.GetCount()!=m
.m_args
.GetCount()) {
345 wxLogMessage("Method '%s' has %d arguments while '%s' has %d arguments",
346 m_strName
, m_args
.GetCount(), m_strName
, m
.m_args
.GetCount());
351 // compare argument types
352 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
353 if (m_args
[i
] != m
.m_args
[i
])
359 bool wxMethod::ActsAsDefaultCtor() const
364 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
365 if (!m_args
[i
].HasDefaultValue())
371 bool wxMethod::operator==(const wxMethod
& m
) const
374 if (IsConst() != m
.IsConst() ||
375 IsStatic() != m
.IsStatic() ||
376 IsVirtual() != m
.IsVirtual() ||
377 IsPureVirtual() != m
.IsPureVirtual() ||
378 IsDeprecated() != m
.IsDeprecated() ||
379 GetAccessSpecifier() != m
.GetAccessSpecifier())
383 wxLogMessage("The method '%s' does not match method '%s'; different attributes", GetName(), m
.GetName());
389 // check everything else
390 return MatchesExceptForAttributes(m
);
393 wxString
wxMethod::GetAsString(bool bWithArgumentNames
, bool bCleanDefaultValues
,
394 bool bDeprecated
, bool bAccessSpec
) const
398 // NOTE: for return and argument types, never use wxType::GetAsCleanString
399 // since in that way we'd miss important decorators like &,*,const etc
401 if (m_retType
!=wxEmptyType
)
402 ret
+= m_retType
.GetAsString() + " ";
403 //else; this is a ctor or dtor
405 ret
+= m_strName
+ "(";
407 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
409 ret
+= m_args
[i
].GetAsString();
411 const wxString
& name
= m_args
[i
].GetArgumentName();
412 if (bWithArgumentNames
&& !name
.IsEmpty())
415 const wxString
& def
= bCleanDefaultValues
?
416 m_args
[i
].GetDefaultCleanValue() : m_args
[i
].GetDefaultValue();
423 if (m_args
.GetCount()>0)
424 ret
= ret
.Left(ret
.Len()-2);
431 ret
= "static " + ret
;
432 if (m_bVirtual
|| m_bPureVirtual
)
433 ret
= "virtual " + ret
;
436 if (m_bDeprecated
&& bDeprecated
)
437 ret
+= " [deprecated]";
446 case wxMAS_PROTECTED
:
447 ret
+= " [protected]";
458 void wxMethod::Dump(wxTextOutputStream
& stream
) const
460 stream
<< "[" + m_retType
.GetAsString() + "]";
461 stream
<< "[" + m_strName
+ "]";
463 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
464 stream
<< "[" + m_args
[i
].GetAsString() + " " + m_args
[i
].GetArgumentName() +
465 "=" + m_args
[i
].GetDefaultValue() + "]";
472 stream
<< " VIRTUAL";
474 stream
<< " PURE-VIRTUAL";
476 stream
<< " DEPRECATED";
481 // ----------------------------------------------------------------------------
483 // ----------------------------------------------------------------------------
485 wxString
wxClass::GetNameWithoutTemplate() const
487 // NB: I'm not sure this is the right terminology for this function!
489 if (m_strName
.Contains("<"))
490 return m_strName
.Left(m_strName
.Find("<"));
494 bool wxClass::IsValidCtorForThisClass(const wxMethod
& m
) const
496 // remember that e.g. the ctor for wxWritableCharTypeBuffer<wchar_t> is
497 // named wxWritableCharTypeBuffer, without the <...> part!
499 if (m
.IsCtor() && m
.GetName() == GetNameWithoutTemplate())
505 bool wxClass::IsValidDtorForThisClass(const wxMethod
& m
) const
507 if (m
.IsDtor() && m
.GetName() == "~" + GetNameWithoutTemplate())
513 void wxClass::Dump(wxTextOutputStream
& out
) const
515 out
<< m_strName
+ "\n";
517 for (unsigned int i
=0; i
<m_methods
.GetCount(); i
++) {
519 // dump all our methods
521 m_methods
[i
].Dump(out
);
528 bool wxClass::CheckConsistency() const
530 for (unsigned int i
=0; i
<m_methods
.GetCount(); i
++)
531 for (unsigned int j
=0; j
<m_methods
.GetCount(); j
++)
532 if (i
!=j
&& m_methods
[i
] == m_methods
[j
])
534 wxLogError("class %s has two methods with the same prototype: '%s'",
535 m_strName
, m_methods
[i
].GetAsString());
539 //((wxClass*)this)->m_methods.RemoveAt(j);
546 const wxMethod
* wxClass::FindMethod(const wxMethod
& m
) const
548 for (unsigned int i
=0; i
<m_methods
.GetCount(); i
++)
549 if (m_methods
[i
] == m
)
550 return &m_methods
[i
];
554 const wxMethod
* wxClass::RecursiveUpwardFindMethod(const wxMethod
& m
,
555 const wxXmlInterface
* allclasses
) const
557 // first, search into *this
558 const wxMethod
* ret
= FindMethod(m
);
562 // then, search into its parents
563 for (unsigned int i
=0; i
<m_parents
.GetCount(); i
++)
565 // ignore non-wx-classes parents
566 // AD-HOC FIX: discard wxScrolledT_Helper parent as it always gives errors
567 if (m_parents
[i
].StartsWith("wx") && m_parents
[i
] != "wxScrolledT_Helper")
569 const wxClass
*parent
= allclasses
->FindClass(m_parents
[i
]);
571 wxLogError("Could not find parent '%s' of class '%s'...",
572 m_parents
[i
], GetName());
576 const wxMethod
*parentMethod
= parent
->RecursiveUpwardFindMethod(m
, allclasses
);
582 // could not find anything even in parent classes...
586 wxMethodPtrArray
wxClass::FindMethodsNamed(const wxString
& name
) const
588 wxMethodPtrArray ret
;
590 for (unsigned int i
=0; i
<m_methods
.GetCount(); i
++)
591 if (m_methods
[i
].GetName() == name
)
592 ret
.Add(&m_methods
[i
]);
598 wxMethodPtrArray
wxClass::RecursiveUpwardFindMethodsNamed(const wxString
& name
,
599 const wxXmlInterface
* allclasses
) const
601 // first, search into *this
602 wxMethodPtrArray ret
= FindMethodsNamed(name
);
603 if (ret
.GetCount()>0)
604 return ret
; // stop here, don't look upward in the parents
606 // then, search into parents of this class
607 for (unsigned int i
=0; i
<m_parents
.GetCount(); i
++)
609 // AD-HOC FIX: discard wxScrolledT_Helper parent as it always gives errors
610 if (m_parents
[i
].StartsWith("wx") && m_parents
[i
] != "wxScrolledT_Helper")
612 const wxClass
*parent
= allclasses
->FindClass(m_parents
[i
]);
614 wxLogError("Could not find parent '%s' of class '%s'...",
615 m_parents
[i
], GetName());
619 wxMethodPtrArray temp
= parent
->RecursiveUpwardFindMethodsNamed(name
, allclasses
);
620 WX_APPEND_ARRAY(ret
, temp
);
629 // ----------------------------------------------------------------------------
631 // ----------------------------------------------------------------------------
633 WX_DEFINE_SORTED_ARRAY(wxClass
*, wxSortedClassArray
);
635 int CompareWxClassObjects(wxClass
*item1
, wxClass
*item2
)
637 // sort alphabetically
638 return item1
->GetName().Cmp(item2
->GetName());
641 void wxXmlInterface::Dump(const wxString
& filename
)
643 wxFFileOutputStream
apioutput( filename
);
644 wxTextOutputStream
apiout( apioutput
);
646 // dump the classes in alphabetical order
647 wxSortedClassArray
sorted(CompareWxClassObjects
);
648 sorted
.Alloc(m_classes
.GetCount());
651 for (i
=0; i
<m_classes
.GetCount(); i
++)
652 sorted
.Add(&m_classes
[i
]);
654 // now they have been sorted
655 for (i
=0; i
<sorted
.GetCount(); i
++)
656 sorted
[i
]->Dump(apiout
);
659 bool wxXmlInterface::CheckConsistency() const
661 // this check can be quite slow, so do it only for debug releases:
663 for (unsigned int i
=0; i
<m_classes
.GetCount(); i
++)
665 if (!m_classes
[i
].CheckConsistency())
668 for (unsigned int j
=0; j
<m_classes
.GetCount(); j
++)
669 if (i
!=j
&& m_classes
[i
].GetName() == m_classes
[j
].GetName())
671 wxLogError("two classes have the same name: %s",
672 m_classes
[i
].GetName());
681 wxClassPtrArray
wxXmlInterface::FindClassesDefinedIn(const wxString
& headerfile
) const
685 for (unsigned int i
=0; i
<m_classes
.GetCount(); i
++)
686 if (m_classes
[i
].GetHeader() == headerfile
)
687 ret
.Add(&m_classes
[i
]);
693 // ----------------------------------------------------------------------------
694 // wxXmlGccInterface helper declarations
695 // ----------------------------------------------------------------------------
697 // or-able flags for a toResolveTypeItem->attrib:
698 #define ATTRIB_CONST 1
699 #define ATTRIB_REFERENCE 2
700 #define ATTRIB_POINTER 4
701 #define ATTRIB_ARRAY 8
703 // it may sound strange but gccxml, in order to produce shorter ID names
704 // uses (after the underscore) characters in range 0-9 and a-z in the ID names;
705 // in order to be able to translate such strings into numbers using strtoul()
706 // we use as base 10 (possible digits) + 25 (possible characters) = 35
707 #define GCCXML_BASE 35
709 class toResolveTypeItem
712 toResolveTypeItem() { attribs
=0; }
713 toResolveTypeItem(unsigned int refID
, unsigned int attribint
)
714 : ref(refID
), attribs(attribint
) {}
716 unsigned long ref
, // the referenced type's ID
717 attribs
; // the attributes of this reference
722 // for wxToResolveTypeHashMap, keys == gccXML IDs and values == toResolveTypeItem
723 WX_DECLARE_HASH_MAP( unsigned long, toResolveTypeItem
,
724 wxIntegerHash
, wxIntegerEqual
,
725 wxToResolveTypeHashMap
);
727 // for wxClassMemberIdHashMap, keys == gccXML IDs and values == wxClass which owns that member ID
728 WX_DECLARE_HASH_MAP( unsigned long, wxClass
*,
729 wxIntegerHash
, wxIntegerEqual
,
730 wxClassMemberIdHashMap
);
734 typedef std::map
<unsigned long, toResolveTypeItem
> wxToResolveTypeHashMap
;
738 // utility to parse gccXML ID values;
739 // this function is equivalent to wxString(str).Mid(1).ToULong(&id, GCCXML_BASE)
740 // but is a little bit faster
741 bool getID(unsigned long *id
, const wxString
& str
)
743 const wxStringCharType
* const start
= str
.wx_str()+1;
744 wxStringCharType
*end
;
745 #if wxUSE_UNICODE_WCHAR
746 unsigned long val
= wcstoul(start
, &end
, GCCXML_BASE
);
748 unsigned long val
= strtoul(start
, &end
, GCCXML_BASE
);
751 // return true only if scan was stopped by the terminating NUL and
752 // if the string was not empty to start with and no under/overflow
754 if ( *end
!= '\0' || end
== start
|| errno
== ERANGE
|| errno
== EINVAL
)
761 // utility specialized to parse efficiently the gccXML list of IDs which occur
762 // in nodes like <Class> ones... i.e. numeric values separated by " _" token
763 bool getMemberIDs(wxClassMemberIdHashMap
* map
, wxClass
* p
, const wxString
& str
)
765 const wxStringCharType
* const start
= str
.wx_str();
766 #if wxUSE_UNICODE_WCHAR
767 size_t len
= wcslen(start
);
769 size_t len
= strlen(start
);
772 if (len
== 0 || start
[0] != '_')
775 const wxStringCharType
*curpos
= start
,
777 wxStringCharType
*nexttoken
;
781 // curpos always points to the underscore of the next token to parse:
782 #if wxUSE_UNICODE_WCHAR
783 unsigned long id
= wcstoul(curpos
+1, &nexttoken
, GCCXML_BASE
);
785 unsigned long id
= strtoul(curpos
+1, &nexttoken
, GCCXML_BASE
);
787 if ( *nexttoken
!= ' ' || errno
== ERANGE
|| errno
== EINVAL
)
790 // advance current position
791 curpos
= nexttoken
+ 1;
793 // add this ID to the hashmap
794 wxClassMemberIdHashMap::value_type
v(id
, p
);
802 // ----------------------------------------------------------------------------
804 // ----------------------------------------------------------------------------
806 bool wxXmlGccInterface::Parse(const wxString
& filename
)
812 wxLogMessage("Parsing %s...", filename
);
814 if (!doc
.Load(filename
)) {
815 wxLogError("can't load %s", filename
);
819 // start processing the XML file
820 if (doc
.GetRoot()->GetName() != "GCC_XML") {
821 wxLogError("invalid root node for %s", filename
);
825 wxString version
= doc
.GetRoot()->GetAttribute("cvs_revision");
828 #define MIN_REVISION 120
830 if (!version
.StartsWith("1."))
834 unsigned long rev
= 0;
835 if (!version
.Mid(2).ToULong(&rev
))
838 if (rev
< MIN_REVISION
)
844 wxLogError("The version of GCC-XML used for the creation of %s is too old; "
845 "the cvs_revision attribute of the root node reports '%s', "
846 "minimal required is 1.%d.", filename
, version
, MIN_REVISION
);
850 wxToResolveTypeHashMap toResolveTypes
;
851 wxClassMemberIdHashMap members
;
852 wxTypeIdHashMap types
;
853 wxTypeIdHashMap files
;
854 wxTypeIdHashMap typedefs
;
856 // prealloc quite a lot of memory!
857 m_classes
.Alloc(ESTIMATED_NUM_CLASSES
);
859 // build a list of wx classes and in general of all existent types
860 child
= doc
.GetRoot()->GetChildren();
863 const wxString
& n
= child
->GetName();
865 unsigned long id
= 0;
866 if (!getID(&id
, child
->GetAttribute("id")) || (id
== 0 && n
!= "File")) {
868 // NOTE: <File> nodes can have an id == "f0"...
870 wxLogError("Invalid id for node %s: %s", n
, child
->GetAttribute("id"));
876 wxString cname
= child
->GetAttribute("name");
877 if (cname
.IsEmpty()) {
878 wxLogError("Invalid empty name for '%s' node", n
);
882 // only register wx classes (do remember also the IDs of their members)
883 if (cname
.StartsWith("wx"))
885 // NB: "file" attribute contains an ID value that we'll resolve later
886 m_classes
.Add(wxClass(cname
, child
->GetAttribute("file")));
888 // the just-inserted class:
889 wxClass
*newClass
= &m_classes
.Last();
891 // now get a list of the base classes:
892 wxXmlNode
*baseNode
= child
->GetChildren();
895 // for now we store as "parents" only the parent IDs...
896 // later we will resolve them into full class names
897 if (baseNode
->GetName() == "Base")
898 newClass
->AddParent(baseNode
->GetAttribute("type"));
900 baseNode
= baseNode
->GetNext();
903 const wxString
& ids
= child
->GetAttribute("members");
906 if (child
->GetAttribute("incomplete") != "1") {
907 wxLogError("Invalid member IDs for '%s' class node: %s",
908 cname
, child
->GetAttribute("id"));
911 //else: don't warn the user; it looks like "incomplete" classes
912 // never have any member...
916 // decode the non-empty list of IDs:
917 if (!getMemberIDs(&members
, newClass
, ids
)) {
918 wxLogError("Invalid member IDs for '%s' class node: %s",
919 cname
, child
->GetAttribute("id"));
925 // register this class also as possible return/argument type:
928 else if (n
== "Typedef")
930 unsigned long typeId
= 0;
931 if (!getID(&typeId
, child
->GetAttribute("type"))) {
932 wxLogError("Invalid type for node %s: %s", n
, child
->GetAttribute("type"));
936 // this typedef node tell us that every type referenced with the
937 // "typeId" ID should be called with another name:
938 wxString name
= child
->GetAttribute("name");
940 // save this typedef in a separate hashmap...
941 typedefs
[typeId
] = name
;
945 else if (n
== "PointerType" || n
== "ReferenceType" ||
946 n
== "CvQualifiedType" || n
== "ArrayType")
948 unsigned long type
= 0;
949 if (!getID(&type
, child
->GetAttribute("type")) || type
== 0) {
950 wxLogError("Invalid type for node %s: %s", n
, child
->GetAttribute("type"));
954 unsigned long attr
= 0;
955 if (n
== "PointerType")
956 attr
= ATTRIB_POINTER
;
957 else if (n
== "ReferenceType")
958 attr
= ATTRIB_REFERENCE
;
959 else if (n
== "CvQualifiedType" && child
->GetAttribute("const") == "1")
961 else if (n
== "ArrayType")
964 // these nodes make reference to other types... we'll resolve them later
965 toResolveTypes
[id
] = toResolveTypeItem(type
, attr
);
967 else if (n
== "FunctionType" || n
== "MethodType")
970 TODO: parsing FunctionType and MethodType nodes is not as easy
971 as for other "simple" types.
975 wxXmlNode
*arg
= child
->GetChildren();
978 if (arg
->GetName() == "Argument")
979 argstr
+= arg
->GetAttribute("type") + ", ";
980 arg
= arg
->GetNext();
983 if (argstr
.Len() > 0)
984 argstr
= argstr
.Left(argstr
.Len()-2); // remove final comma
986 // these nodes make reference to other types... we'll resolve them later
987 //toResolveTypes[id] = toResolveTypeItem(ret, 0);
988 //types[id] = child->GetAttribute("returns") + "(" + argstr + ")";
990 types
[id
] = "TOFIX"; // typically this type will be "fixed" thanks
991 // to a typedef later...
993 else if (n
== "File")
995 if (!child
->GetAttribute("id").StartsWith("f")) {
996 wxLogError("Unexpected file ID: %s", child
->GetAttribute("id"));
1000 // just ignore this node... all file IDs/names were already parsed
1001 files
[id
] = child
->GetAttribute("name");
1005 // we register everything else as a possible return/argument type:
1006 const wxString
& name
= child
->GetAttribute("name");
1008 if (!name
.IsEmpty())
1011 //typeNames.Add(name);
1016 // this may happen with unnamed structs/union, special ctors,
1017 // or other exotic things which we are not interested to, since
1018 // they're never used as return/argument types by wxWidgets methods
1022 wxLogWarning("Type node '%s' with ID '%s' does not have name attribute",
1023 n
, child
->GetAttribute("id"));
1026 types
[id
] = "TOFIX";
1030 child
= child
->GetNext();
1032 // give feedback to the user about the progress...
1033 if ((++nodes%PROGRESS_RATE
)==0) ShowProgress();
1036 // some nodes with IDs referenced by methods as return/argument types, do reference
1037 // in turn other nodes (see PointerType, ReferenceType and CvQualifierType above);
1038 // thus we need to resolve their name iteratively:
1039 while (toResolveTypes
.size()>0)
1043 wxLogMessage("%d types were collected; %d types need yet to be resolved...",
1044 types
.size(), toResolveTypes
.size());
1047 for (wxToResolveTypeHashMap::iterator i
= toResolveTypes
.begin();
1048 i
!= toResolveTypes
.end();)
1050 unsigned long id
= i
->first
;
1051 unsigned long referenced
= i
->second
.ref
;
1053 wxTypeIdHashMap::iterator primary
= types
.find(referenced
);
1054 if (primary
!= types
.end())
1056 // this to-resolve-type references a "primary" type
1058 wxString newtype
= primary
->second
;
1059 int attribs
= i
->second
.attribs
;
1061 // attribs may contain a combination of ATTRIB_* flags:
1062 if (attribs
& ATTRIB_CONST
)
1063 newtype
= "const " + newtype
;
1064 if (attribs
& ATTRIB_REFERENCE
)
1065 newtype
= newtype
+ "&";
1066 if (attribs
& ATTRIB_POINTER
)
1067 newtype
= newtype
+ "*";
1068 if (attribs
& ATTRIB_ARRAY
)
1069 newtype
= newtype
+ "[]";
1071 // add the resolved type to the list of "primary" types
1072 if (newtype
.Contains("TOFIX") && typedefs
[id
] != "")
1073 types
[id
] = typedefs
[id
]; // better use a typedef for this type!
1075 types
[id
] = newtype
;
1077 // this one has been resolved; erase it through its iterator!
1078 toResolveTypes
.erase(i
);
1080 // now iterator i is invalid; assign it again to the beginning
1081 i
= toResolveTypes
.begin();
1085 // then search in the referenced types themselves:
1086 wxToResolveTypeHashMap::iterator idx2
= toResolveTypes
.find(referenced
);
1087 if (idx2
!= toResolveTypes
.end())
1089 // merge this to-resolve-type with the idx2->second type
1090 i
->second
.ref
= idx2
->second
.ref
;
1091 i
->second
.attribs
|= idx2
->second
.attribs
;
1093 // this type will eventually be solved in the next while() iteration
1098 wxLogError("Cannot solve '%d' reference type!", referenced
);
1105 // resolve header names
1107 for (i
=0; i
<m_classes
.GetCount(); i
++)
1109 unsigned long fileID
= 0;
1110 if (!getID(&fileID
, m_classes
[i
].GetHeader()) || fileID
== 0) {
1111 wxLogError("invalid header id: %s", m_classes
[i
].GetHeader());
1116 wxTypeIdHashMap::const_iterator idx
= files
.find(fileID
);
1117 if (idx
== files
.end())
1119 // this is an error!
1120 wxLogError("couldn't find file ID '%s'", m_classes
[i
].GetHeader());
1123 m_classes
[i
].SetHeader(idx
->second
);
1126 // resolve parent names
1127 for (i
=0; i
<m_classes
.GetCount(); i
++)
1129 for (unsigned int k
=0; k
<m_classes
[i
].GetParentCount(); k
++)
1133 if (!getID(&id
, m_classes
[i
].GetParent(k
))) {
1134 wxLogError("invalid parent class ID for '%s'", m_classes
[i
].GetName());
1138 wxTypeIdHashMap::const_iterator idx
= types
.find(id
);
1139 if (idx
== types
.end())
1141 // this is an error!
1142 wxLogError("couldn't find parent class ID '%d'", id
);
1145 // replace k-th parent with its true name:
1146 m_classes
[i
].SetParent(k
, idx
->second
);
1150 // build the list of the wx methods
1151 child
= doc
.GetRoot()->GetChildren();
1154 wxString n
= child
->GetName(), acc
= child
->GetAttribute("access");
1156 // only register public&protected methods
1157 if ((acc
== "public" || acc
== "protected") &&
1158 (n
== "Method" || n
== "Constructor" || n
== "Destructor" || n
== "OperatorMethod"))
1160 unsigned long id
= 0;
1161 if (!getID(&id
, child
->GetAttribute("id"))) {
1162 wxLogError("invalid ID for node '%s' with ID '%s'", n
, child
->GetAttribute("id"));
1166 wxClassMemberIdHashMap::const_iterator it
= members
.find(id
);
1167 if (it
!= members
.end())
1169 wxClass
*p
= it
->second
;
1171 // this <Method> node is a method of the i-th class!
1173 if (!ParseMethod(child
, types
, newfunc
)) {
1174 wxLogError("The method '%s' could not be added to class '%s'",
1175 child
->GetAttribute("demangled"), p
->GetName());
1179 // do some additional check that we can do only here:
1181 if (newfunc
.IsCtor() && !p
->IsValidCtorForThisClass(newfunc
)) {
1182 wxLogError("The method '%s' does not seem to be a ctor for '%s'",
1183 newfunc
.GetName(), p
->GetName());
1186 if (newfunc
.IsDtor() && !p
->IsValidDtorForThisClass(newfunc
)) {
1187 wxLogError("The method '%s' does not seem to be a dtor for '%s'",
1188 newfunc
.GetName(), p
->GetName());
1192 p
->AddMethod(newfunc
);
1196 child
= child
->GetNext();
1198 // give feedback to the user about the progress...
1199 if ((++nodes%PROGRESS_RATE
)==0) ShowProgress();
1202 if (!CheckConsistency())
1203 return false; // the check failed
1208 bool wxXmlGccInterface::ParseMethod(const wxXmlNode
*p
,
1209 const wxTypeIdHashMap
& types
,
1212 // get the real name
1213 wxString name
= p
->GetAttribute("name").Strip(wxString::both
);
1214 if (p
->GetName() == "Destructor")
1216 else if (p
->GetName() == "OperatorMethod")
1217 name
= "operator" + name
;
1219 // resolve return type
1221 unsigned long retid
= 0;
1222 if (!getID(&retid
, p
->GetAttribute("returns")) || retid
== 0)
1224 if (p
->GetName() != "Destructor" && p
->GetName() != "Constructor") {
1225 wxLogError("Empty return ID for method '%s', with ID '%s'",
1226 name
, p
->GetAttribute("id"));
1232 wxTypeIdHashMap::const_iterator retidx
= types
.find(retid
);
1233 if (retidx
== types
.end()) {
1234 wxLogError("Could not find return type ID '%s'", retid
);
1238 ret
= wxType(retidx
->second
);
1240 wxLogError("Invalid return type '%s' for method '%s', with ID '%s'",
1241 retidx
->second
, name
, p
->GetAttribute("id"));
1246 // resolve argument types
1247 wxArgumentTypeArray argtypes
;
1248 wxXmlNode
*arg
= p
->GetChildren();
1251 if (arg
->GetName() == "Argument")
1253 unsigned long id
= 0;
1254 if (!getID(&id
, arg
->GetAttribute("type")) || id
== 0) {
1255 wxLogError("Invalid argument type ID '%s' for method '%s' with ID %s",
1256 arg
->GetAttribute("type"), name
, p
->GetAttribute("id"));
1260 wxTypeIdHashMap::const_iterator idx
= types
.find(id
);
1261 if (idx
== types
.end()) {
1262 wxLogError("Could not find argument type ID '%s'", id
);
1266 argtypes
.Add(wxArgumentType(idx
->second
,
1267 arg
->GetAttribute("default"),
1268 arg
->GetAttribute("name")));
1271 arg
= arg
->GetNext();
1274 m
.SetReturnType(ret
);
1276 m
.SetArgumentTypes(argtypes
);
1277 m
.SetConst(p
->GetAttribute("const") == "1");
1278 m
.SetStatic(p
->GetAttribute("static") == "1");
1280 // NOTE: gccxml is smart enough to mark as virtual those functions
1281 // which are declared virtual in base classes but don't have
1282 // the "virtual" keyword explicitly indicated in the derived
1283 // classes... so we don't need any further logic for virtuals
1285 m
.SetVirtual(p
->GetAttribute("virtual") == "1");
1286 m
.SetPureVirtual(p
->GetAttribute("pure_virtual") == "1");
1287 m
.SetDeprecated(p
->GetAttribute("attributes") == "deprecated");
1289 // decode access specifier
1290 if (p
->GetAttribute("access") == "public")
1291 m
.SetAccessSpecifier(wxMAS_PUBLIC
);
1292 else if (p
->GetAttribute("access") == "protected")
1293 m
.SetAccessSpecifier(wxMAS_PROTECTED
);
1294 else if (p
->GetAttribute("access") == "private")
1295 m
.SetAccessSpecifier(wxMAS_PRIVATE
);
1298 wxLogError("The prototype '%s' is not valid!", m
.GetAsString());
1307 // ----------------------------------------------------------------------------
1308 // wxXmlDoxygenInterface global helpers
1309 // ----------------------------------------------------------------------------
1311 static wxString
GetTextFromChildren(const wxXmlNode
*n
)
1315 // consider the tree
1317 // <a><b>this</b> is a <b>string</b></a>
1326 // unlike wxXmlNode::GetNodeContent() which would return " is a "
1327 // this function returns "this is a string"
1329 wxXmlNode
*ref
= n
->GetChildren();
1331 if (ref
->GetType() == wxXML_ELEMENT_NODE
)
1332 text
+= ref
->GetNodeContent();
1333 else if (ref
->GetType() == wxXML_TEXT_NODE
)
1334 text
+= ref
->GetContent();
1336 wxLogWarning("Unexpected node type while getting text from '%s' node", n
->GetName());
1338 ref
= ref
->GetNext();
1344 static bool HasTextNodeContaining(const wxXmlNode
*parent
, const wxString
& name
)
1349 wxXmlNode
*p
= parent
->GetChildren();
1352 switch (p
->GetType())
1354 case wxXML_TEXT_NODE
:
1355 if (p
->GetContent() == name
)
1359 case wxXML_ELEMENT_NODE
:
1360 // recurse into this node...
1361 if (HasTextNodeContaining(p
, name
))
1376 static const wxXmlNode
* FindNodeNamed(const wxXmlNode
* parent
, const wxString
& name
)
1381 const wxXmlNode
*p
= parent
->GetChildren();
1384 if (p
->GetName() == name
)
1387 // search recursively in the children of this node
1388 const wxXmlNode
*ret
= FindNodeNamed(p
, name
);
1398 int GetAvailabilityFor(const wxXmlNode
*node
)
1400 // identify <onlyfor> custom XML tags
1401 const wxXmlNode
* onlyfor
= FindNodeNamed(node
, "onlyfor");
1403 return wxPORT_UNKNOWN
;
1405 wxArrayString ports
= wxSplit(onlyfor
->GetNodeContent(), ',');
1406 int nAvail
= wxPORT_UNKNOWN
;
1407 for (unsigned int i
=0; i
< ports
.GetCount(); i
++)
1409 if (!ports
[i
].StartsWith("wx")) {
1410 wxLogError("unexpected port ID '%s'", ports
[i
]);
1414 nAvail
|= wxPlatformInfo::GetPortId(ports
[i
].Mid(2));
1421 // ----------------------------------------------------------------------------
1422 // wxXmlDoxygenInterface
1423 // ----------------------------------------------------------------------------
1425 bool wxXmlDoxygenInterface::Parse(const wxString
& filename
)
1427 wxXmlDocument index
;
1428 wxXmlNode
*compound
;
1430 wxLogMessage("Parsing %s...", filename
);
1432 if (!index
.Load(filename
)) {
1433 wxLogError("can't load %s", filename
);
1437 // start processing the index:
1438 if (index
.GetRoot()->GetName() != "doxygenindex") {
1439 wxLogError("invalid root node for %s", filename
);
1444 NB: we may need in future to do a version-check here if the
1445 format of the XML generated by doxygen changes.
1446 For now (doxygen version 1.5.5), this check is not required
1447 since AFAIK the XML format never changed since it was introduced.
1450 m_classes
.Alloc(ESTIMATED_NUM_CLASSES
);
1452 // process files referenced by this index file
1453 compound
= index
.GetRoot()->GetChildren();
1456 if (compound
->GetName() == "compound" &&
1457 compound
->GetAttribute("kind") == "class")
1459 wxString refid
= compound
->GetAttribute("refid");
1461 wxFileName
fn(filename
);
1462 if (!ParseCompoundDefinition(fn
.GetPath(wxPATH_GET_SEPARATOR
) + refid
+ ".xml"))
1466 compound
= compound
->GetNext();
1470 if (!CheckConsistency())
1471 return false; // the check failed
1476 bool wxXmlDoxygenInterface::ParseCompoundDefinition(const wxString
& filename
)
1478 wxClassMemberIdHashMap parents
;
1485 wxLogMessage("Parsing %s...", filename
);
1488 if (!doc
.Load(filename
)) {
1489 wxLogError("can't load %s", filename
);
1493 // start processing this compound definition XML
1494 if (doc
.GetRoot()->GetName() != "doxygen") {
1495 wxLogError("invalid root node for %s", filename
);
1499 // build a list of wx classes
1500 child
= doc
.GetRoot()->GetChildren();
1503 if (child
->GetName() == "compounddef" &&
1504 child
->GetAttribute("kind") == "class")
1508 wxString absoluteFile
, header
;
1510 wxXmlNode
*subchild
= child
->GetChildren();
1513 // NOTE: when documenting functions using the //@{ and //@}
1514 // tags to create function groups, doxygen puts the
1515 // contained methods into a "user-defined" section
1516 // so we _must_ use the "prot" attribute to distinguish
1517 // public/protected methods from private ones and cannot
1518 // rely on the kind="public" attribute of <sectiondef>
1519 if (subchild
->GetName() == "sectiondef")
1521 wxXmlNode
*membernode
= subchild
->GetChildren();
1524 const wxString
& accessSpec
= membernode
->GetAttribute("prot");
1526 // parse only public&protected functions:
1527 if (membernode
->GetName() == "memberdef" &&
1528 membernode
->GetAttribute("kind") == "function" &&
1529 (accessSpec
== "public" || accessSpec
== "protected"))
1532 if (!ParseMethod(membernode
, m
, header
)) {
1533 wxLogError("The method '%s' could not be added to class '%s'",
1534 m
.GetName(), klass
.GetName());
1538 if (accessSpec
== "public")
1539 m
.SetAccessSpecifier(wxMAS_PUBLIC
);
1540 else if (accessSpec
== "protected")
1541 m
.SetAccessSpecifier(wxMAS_PROTECTED
);
1542 else if (accessSpec
== "private")
1543 m
.SetAccessSpecifier(wxMAS_PRIVATE
);
1545 if (absoluteFile
.IsEmpty())
1546 absoluteFile
= header
;
1547 else if (header
!= absoluteFile
)
1549 wxLogError("Found inconsistency in the XML file '%s': "
1550 "the method '%s' is documented in the "
1551 "file '%s' but the other methods of the same "
1552 "class are documented in the file '%s'",
1553 filename
, m
.GetName(), header
, absoluteFile
);
1560 membernode
= membernode
->GetNext();
1563 // all methods of this class were taken from the header "absoluteFile":
1564 klass
.SetHeader(absoluteFile
);
1566 else if (subchild
->GetName() == "compoundname")
1568 klass
.SetName(subchild
->GetNodeContent());
1570 /*else if (subchild->GetName() == "includes")
1572 // NOTE: we'll get the header from the <location> tags
1573 // scattered inside <memberdef> tags instead of
1574 // this <includes> tag since it does not contain
1575 // the absolute path of the header
1577 klass.SetHeader(subchild->GetNodeContent());
1579 else if (subchild
->GetName() == "detaileddescription")
1581 // identify <onlyfor> custom XML tags
1582 klass
.SetAvailability(GetAvailabilityFor(subchild
));
1584 else if (subchild
->GetName() == "basecompoundref")
1586 // add the name of this parent to the list of klass' parents
1587 klass
.AddParent(subchild
->GetNodeContent());
1590 subchild
= subchild
->GetNext();
1596 m_classes
.Add(klass
);
1600 wxLogWarning("discarding class '%s' with %d methods...",
1601 klass
.GetName(), klass
.GetMethodCount());
1605 child
= child
->GetNext();
1607 // give feedback to the user about the progress...
1608 if ((++nodes%PROGRESS_RATE
)==0) ShowProgress();
1614 bool wxXmlDoxygenInterface::ParseMethod(const wxXmlNode
* p
, wxMethod
& m
, wxString
& header
)
1616 wxArgumentTypeArray args
;
1619 wxXmlNode
*child
= p
->GetChildren();
1622 if (child
->GetName() == "name")
1623 m
.SetName(child
->GetNodeContent());
1624 else if (child
->GetName() == "type")
1625 m
.SetReturnType(wxType(GetTextFromChildren(child
)));
1626 else if (child
->GetName() == "param")
1628 wxString typestr
, namestr
, defstr
, arrstr
;
1629 wxXmlNode
*n
= child
->GetChildren();
1632 if (n
->GetName() == "type")
1633 // if the <type> node has children, they should be all TEXT and <ref> nodes
1634 // and we need to take the text they contain, in the order they appear
1635 typestr
= GetTextFromChildren(n
);
1636 else if (n
->GetName() == "declname")
1637 namestr
= GetTextFromChildren(n
);
1638 else if (n
->GetName() == "defval")
1639 defstr
= GetTextFromChildren(n
).Strip(wxString::both
);
1640 else if (n
->GetName() == "array")
1641 arrstr
= GetTextFromChildren(n
);
1646 if (typestr
.IsEmpty()) {
1647 wxLogError("cannot find type node for a param in method '%s'", m
.GetName());
1651 wxArgumentType
newarg(typestr
+ arrstr
, defstr
, namestr
);
1653 // can we use preprocessor output to transform the default value
1654 // into the same form which gets processed by wxXmlGccInterface?
1655 wxStringHashMap::const_iterator it
= m_preproc
.find(defstr
);
1656 if (it
!= m_preproc
.end())
1657 newarg
.SetDefaultValue(defstr
, it
->second
);
1661 else if (child
->GetName() == "location")
1664 if (child
->GetAttribute("line").ToLong(&line
))
1665 m
.SetLocation((int)line
);
1666 header
= child
->GetAttribute("file");
1668 else if (child
->GetName() == "detaileddescription")
1670 // when a method has a @deprecated tag inside its description,
1671 // Doxygen outputs somewhere nested inside <detaileddescription>
1672 // a <xreftitle>Deprecated</xreftitle> tag.
1673 m
.SetDeprecated(HasTextNodeContaining(child
, "Deprecated"));
1675 // identify <onlyfor> custom XML tags
1676 m
.SetAvailability(GetAvailabilityFor(child
));
1679 child
= child
->GetNext();
1682 m
.SetArgumentTypes(args
);
1683 m
.SetConst(p
->GetAttribute("const")=="yes");
1684 m
.SetStatic(p
->GetAttribute("static")=="yes");
1686 // NOTE: Doxygen is smart enough to mark as virtual those functions
1687 // which are declared virtual in base classes but don't have
1688 // the "virtual" keyword explicitly indicated in the derived
1689 // classes... so we don't need any further logic for virtuals
1691 m
.SetVirtual(p
->GetAttribute("virt")=="virtual");
1692 m
.SetPureVirtual(p
->GetAttribute("virt")=="pure-virtual");
1695 wxLogError("The prototype '%s' is not valid!", m
.GetAsString());