1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: Parser of the API/interface XML files
4 // Author: Francesco Montorsi
7 // Copyright: (c) 2008 Francesco Montorsi
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
11 // For compilers that support precompilation, includes "wx/wx.h".
12 #include "wx/wxprec.h"
18 // for all others, include the necessary headers
23 #include "wx/xml/xml.h"
24 #include "wx/wfstream.h"
25 #include "wx/hashmap.h"
26 #include "wx/filename.h"
27 #include "xmlparser.h"
30 #include <wx/arrimpl.cpp>
31 WX_DEFINE_OBJARRAY(wxTypeArray
)
32 WX_DEFINE_OBJARRAY(wxArgumentTypeArray
)
33 WX_DEFINE_OBJARRAY(wxMethodArray
)
34 WX_DEFINE_OBJARRAY(wxClassArray
)
37 #define PROGRESS_RATE 1000 // each PROGRESS_RATE nodes processed print a dot
38 #define ESTIMATED_NUM_CLASSES 600 // used by both wxXmlInterface-derived classes to prealloc mem
41 // defined in ifacecheck.cpp
42 extern bool g_verbose
;
45 bool g_bLogEnabled
= true;
49 // ----------------------------------------------------------------------------
51 // ----------------------------------------------------------------------------
55 void wxType::SetTypeFromString(const wxString
& t
)
58 TODO: optimize the following code writing a single function
59 which works at char-level and does everything in a single pass
62 // clean the type string
63 // ---------------------
67 // [] is the same as * for gccxml
68 m_strType
.Replace("[]", "*");
69 m_strType
.Replace("long int", "long"); // in wx typically we never write "long int", just "long"
70 m_strType
.Replace("long unsigned int", "unsigned long");
71 m_strType
.Replace("short unsigned int", "unsigned short");
73 // make sure the * and & operator always use the same spacing rules
74 // (to make sure GetAsString() output is always consistent)
75 m_strType
.Replace("*", "* ");
76 m_strType
.Replace("&", "& ");
77 m_strType
.Replace(" *", "*");
78 m_strType
.Replace(" &", "&");
80 while (m_strType
.Contains(" "))
81 m_strType
.Replace(" ", " "); // do it once again
83 m_strType
.Replace(" ,", ",");
86 m_strType
.Replace("_wxArraywxArrayStringBase", "wxString");
87 m_strType
.Replace("ExitCode", "void*"); // used in wxThread stuff
89 m_strType
= m_strType
.Strip(wxString::both
);
93 // clean the type string (this time for the comparison)
94 // ----------------------------------------------------
96 m_strTypeClean
= m_strType
; // begin with the already-cleaned string
97 m_strTypeClean
.Replace("const", "");
98 m_strTypeClean
.Replace("static", "");
99 m_strTypeClean
.Replace("*", "");
100 m_strTypeClean
.Replace("&", "");
101 m_strTypeClean
.Replace("[]", "");
102 m_strTypeClean
= m_strTypeClean
.Strip(wxString::both
);
104 // to avoid false errors types like wxStandardPaths and wxStandardPathsBase
105 // need to be considered as the same type
106 if (m_strTypeClean
.EndsWith("Base"))
107 m_strTypeClean
= m_strTypeClean
.Left(m_strTypeClean
.Len()-4);
109 // remove the namespace from the types; there's no problem of conflicts
110 // (except for templates) and this avoids tons of false warnings
111 if (m_strTypeClean
.Contains("::") && !m_strTypeClean
.Contains("<"))
112 m_strTypeClean
= m_strTypeClean
.Mid(m_strTypeClean
.Find("::")+2);
115 m_strTypeClean
.Replace("wxWindowID", "int");
118 bool wxType::IsOk() const
120 // NB: m_strType can contain the :: operator; think to e.g. the
121 // "reverse_iterator_impl<wxString::const_iterator>" type
122 // It can also contain commas, * and & operators etc
124 return !m_strTypeClean
.IsEmpty();
127 bool wxType::operator==(const wxType
& m
) const
129 // brain-dead comparison:
131 if (m_strTypeClean
== m
.m_strTypeClean
&&
132 IsConst() == m
.IsConst() &&
133 IsStatic() == m
.IsStatic() &&
134 IsPointer() == m
.IsPointer() &&
135 IsReference() == m
.IsReference())
140 wxLogMessage("Type '%s' does not match type '%s'", m_strType
, m
.m_strType
);
141 wxLogMessage(" => TypeClean %s / %s; IsConst %d / %d; IsStatic %d / %d; IsPointer %d / %d; IsReference %d / %d",
142 m_strTypeClean
, m
.m_strTypeClean
, IsConst(), m
.IsConst(),
143 IsStatic(), m
.IsStatic(), IsPointer(), m
.IsPointer(),
144 IsReference(), m
.IsReference());
151 // ----------------------------------------------------------------------------
153 // ----------------------------------------------------------------------------
155 void wxArgumentType::SetDefaultValue(const wxString
& defval
, const wxString
& defvalForCmp
)
157 m_strDefaultValue
= defval
.Strip(wxString::both
);
158 m_strDefaultValueForCmp
= defvalForCmp
.IsEmpty() ?
159 m_strDefaultValue
: defvalForCmp
.Strip(wxString::both
);
162 // clean the default argument strings
163 // ----------------------------------
165 // Note: we adjust the aesthetic form of the m_strDefaultValue string for the "modify mode"
166 // of ifacecheck: we may need to write it out in an interface header
169 for (int i
=0; i
<2; i
++) // to avoid copying&pasting the code!
171 if (i
== 0) p
= &m_strDefaultValue
;
172 if (i
== 1) p
= &m_strDefaultValueForCmp
;
174 if (*p
== "0u" || *p
== "0l") *p
= "0";
176 p
->Replace("0x000000001", "1");
177 p
->Replace("\\000\\000\\000", ""); // fix for unicode strings:
178 p
->Replace("\\011", "\\t");
179 p
->Replace("e+0", "");
180 p
->Replace("2147483647", "__INT_MAX__");
182 // ADHOC-FIX: for wxConv* default values
183 p
->Replace("wxConvAuto(wxFONTENCODING_DEFAULT)", "wxConvAuto()");
184 p
->Replace("wxGet_wxConvUTF8()", "wxConvUTF8");
185 p
->Replace("wxGet_wxConvLocal()", "wxConvLocal");
189 // clean ONLY the default argument string specific for comparison
190 // --------------------------------------------------------------
192 if (m_strDefaultValueForCmp
.StartsWith("wxT(") &&
193 m_strDefaultValueForCmp
.EndsWith(")"))
195 // get rid of the wxT() part
196 unsigned int len
= m_strDefaultValueForCmp
.Len();
197 m_strDefaultValueForCmp
= m_strDefaultValueForCmp
.Mid(4,len
-5);
201 // doxygen likes to put wxDateTime:: in front of all wxDateTime enums;
202 // fix this to avoid false positives
203 m_strDefaultValueForCmp
.Replace("wxDateTime::", "");
204 m_strDefaultValueForCmp
.Replace("wxStockGDI::", ""); // same story for some other classes
205 m_strDefaultValueForCmp
.Replace("wxHelpEvent::", ""); // same story for some other classes
206 m_strDefaultValueForCmp
.Replace("* GetColour(COLOUR_BLACK)", "*wxBLACK");
209 if (m_strDefaultValueForCmp
.Contains("wxGetTranslation"))
210 m_strDefaultValueForCmp
= "_(TOFIX)"; // TODO: wxGetTranslation gives problems to gccxml
213 bool wxArgumentType::operator==(const wxArgumentType
& m
) const
215 if ((const wxType
&)(*this) != (const wxType
&)m
)
218 // check if the default values match
219 // ---------------------------------
223 // default values for style attributes of wxWindow-derived classes in gccxml appear as raw
224 // numbers; avoid false positives in this case!
225 if (m_strArgName
== m
.m_strArgName
&& m_strArgName
== "style" &&
226 (m_strDefaultValueForCmp
.IsNumber() || m
.m_strDefaultValueForCmp
.IsNumber()))
229 // fix for default values which were replaced by gcc-xml with their numeric values
230 // (at this point we know that m_strTypeClean == m.m_strTypeClean):
231 if (m_strTypeClean
== "long" || m_strTypeClean
== "int")
233 if ((m_strDefaultValueForCmp
.IsNumber() && m
.m_strDefaultValueForCmp
.StartsWith("wx")) ||
234 (m
.m_strDefaultValueForCmp
.IsNumber() && m_strDefaultValueForCmp
.StartsWith("wx")))
238 wxLogMessage("Supposing '%s' default value to be the same of '%s'...",
239 m_strDefaultValueForCmp
, m
.m_strDefaultValueForCmp
);
245 else if (m_strTypeClean
== "float" || m_strTypeClean
== "double")
246 // gccXML translates the default floating values in a hardly usable
247 // format; e.g. 25.2 => 2.51999999999999992894572642398998141288757324219e+1
248 // we avoid check on these...
251 if (m_strDefaultValueForCmp
!= m
.m_strDefaultValueForCmp
)
253 // maybe the default values are numbers.
254 // in this case gccXML gives as default values things like '-0x0000001' instead of just '-1'.
255 // To handle these cases, we try to convert the default value strings to numbers:
256 long def1val
, def2val
;
257 if (m_strDefaultValueForCmp
.ToLong(&def1val
, 0 /* auto-detect */) &&
258 m
.m_strDefaultValueForCmp
.ToLong(&def2val
, 0 /* auto-detect */))
260 if (def1val
== def2val
)
261 return true; // the default values match
266 wxLogMessage("Argument type '%s = %s' has different default value from '%s = %s'",
267 m_strType
, m_strDefaultValueForCmp
, m
.m_strType
, m
.m_strDefaultValueForCmp
);
272 // we deliberately avoid checks on the argument name
278 // ----------------------------------------------------------------------------
280 // ----------------------------------------------------------------------------
282 bool wxMethod::IsOk() const
284 // NOTE: m_retType can be a wxEmptyType, and means that this method
285 // is a ctor or a dtor.
286 if (!m_retType
.IsOk() && m_retType
!=wxEmptyType
) {
287 wxLogError("'%s' method has invalid return type: %s", m_retType
.GetAsString());
291 if (m_strName
.IsEmpty())
294 // a function can't be both const and static or virtual and static!
295 if ((m_bConst
&& m_bStatic
) || ((m_bVirtual
|| m_bPureVirtual
) && m_bStatic
)) {
296 wxLogError("'%s' method can't be both const/static or virtual/static", m_strName
);
300 wxASSERT(!m_bPureVirtual
|| (m_bPureVirtual
&& m_bVirtual
));
302 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
303 if (!m_args
[i
].IsOk()) {
304 wxLogError("'%s' method has invalid %d-th argument type: %s",
305 m_strName
, i
+1, m_args
[i
].GetAsString());
309 // NB: the default value of the arguments can contain pretty much everything
310 // (think to e.g. wxPoint(3+4/2,0) or *wxBLACK or someClass<type>)
311 // so we don't do any test on their contents
312 if (m_args
.GetCount()>0)
314 bool previousArgHasDefault
= m_args
[0].HasDefaultValue();
315 for (unsigned int i
=1; i
<m_args
.GetCount(); i
++)
317 if (previousArgHasDefault
&& !m_args
[i
].HasDefaultValue()) {
318 wxLogError("'%s' method has %d-th argument which has no default value "
319 "(while the previous one had one!)",
324 previousArgHasDefault
= m_args
[i
].HasDefaultValue();
331 bool wxMethod::MatchesExceptForAttributes(const wxMethod
& m
) const
333 if (GetReturnType() != m
.GetReturnType() ||
334 GetName() != m
.GetName())
338 wxLogMessage("The method '%s' does not match method '%s'; different names/rettype", GetName(), m
.GetName());
343 if (m_args
.GetCount()!=m
.m_args
.GetCount()) {
346 wxLogMessage("Method '%s' has %d arguments while '%s' has %d arguments",
347 m_strName
, m_args
.GetCount(), m_strName
, m
.m_args
.GetCount());
352 // compare argument types
353 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
354 if (m_args
[i
] != m
.m_args
[i
])
360 bool wxMethod::ActsAsDefaultCtor() const
365 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
366 if (!m_args
[i
].HasDefaultValue())
372 bool wxMethod::operator==(const wxMethod
& m
) const
375 if (IsConst() != m
.IsConst() ||
376 IsStatic() != m
.IsStatic() ||
377 IsVirtual() != m
.IsVirtual() ||
378 IsPureVirtual() != m
.IsPureVirtual() ||
379 IsDeprecated() != m
.IsDeprecated() ||
380 GetAccessSpecifier() != m
.GetAccessSpecifier())
384 wxLogMessage("The method '%s' does not match method '%s'; different attributes", GetName(), m
.GetName());
390 // check everything else
391 return MatchesExceptForAttributes(m
);
394 wxString
wxMethod::GetAsString(bool bWithArgumentNames
, bool bCleanDefaultValues
,
395 bool bDeprecated
, bool bAccessSpec
) const
399 // NOTE: for return and argument types, never use wxType::GetAsCleanString
400 // since in that way we'd miss important decorators like &,*,const etc
402 if (m_retType
!=wxEmptyType
)
403 ret
+= m_retType
.GetAsString() + " ";
404 //else; this is a ctor or dtor
406 ret
+= m_strName
+ "(";
408 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
410 ret
+= m_args
[i
].GetAsString();
412 const wxString
& name
= m_args
[i
].GetArgumentName();
413 if (bWithArgumentNames
&& !name
.IsEmpty())
416 const wxString
& def
= bCleanDefaultValues
?
417 m_args
[i
].GetDefaultCleanValue() : m_args
[i
].GetDefaultValue();
424 if (m_args
.GetCount()>0)
425 ret
= ret
.Left(ret
.Len()-2);
432 ret
= "static " + ret
;
433 if (m_bVirtual
|| m_bPureVirtual
)
434 ret
= "virtual " + ret
;
437 if (m_bDeprecated
&& bDeprecated
)
438 ret
+= " [deprecated]";
447 case wxMAS_PROTECTED
:
448 ret
+= " [protected]";
459 void wxMethod::Dump(wxTextOutputStream
& stream
) const
461 stream
<< "[" + m_retType
.GetAsString() + "]";
462 stream
<< "[" + m_strName
+ "]";
464 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
465 stream
<< "[" + m_args
[i
].GetAsString() + " " + m_args
[i
].GetArgumentName() +
466 "=" + m_args
[i
].GetDefaultValue() + "]";
473 stream
<< " VIRTUAL";
475 stream
<< " PURE-VIRTUAL";
477 stream
<< " DEPRECATED";
482 // ----------------------------------------------------------------------------
484 // ----------------------------------------------------------------------------
486 wxString
wxClass::GetNameWithoutTemplate() const
488 // NB: I'm not sure this is the right terminology for this function!
490 if (m_strName
.Contains("<"))
491 return m_strName
.Left(m_strName
.Find("<"));
495 bool wxClass::IsValidCtorForThisClass(const wxMethod
& m
) const
497 // remember that e.g. the ctor for wxWritableCharTypeBuffer<wchar_t> is
498 // named wxWritableCharTypeBuffer, without the <...> part!
500 if (m
.IsCtor() && m
.GetName() == GetNameWithoutTemplate())
506 bool wxClass::IsValidDtorForThisClass(const wxMethod
& m
) const
508 if (m
.IsDtor() && m
.GetName() == "~" + GetNameWithoutTemplate())
514 void wxClass::Dump(wxTextOutputStream
& out
) const
516 out
<< m_strName
+ "\n";
518 for (unsigned int i
=0; i
<m_methods
.GetCount(); i
++) {
520 // dump all our methods
522 m_methods
[i
].Dump(out
);
529 bool wxClass::CheckConsistency() const
531 for (unsigned int i
=0; i
<m_methods
.GetCount(); i
++)
532 for (unsigned int j
=0; j
<m_methods
.GetCount(); j
++)
533 if (i
!=j
&& m_methods
[i
] == m_methods
[j
])
535 wxLogError("class %s has two methods with the same prototype: '%s'",
536 m_strName
, m_methods
[i
].GetAsString());
540 //((wxClass*)this)->m_methods.RemoveAt(j);
547 const wxMethod
* wxClass::FindMethod(const wxMethod
& m
) const
549 for (unsigned int i
=0; i
<m_methods
.GetCount(); i
++)
550 if (m_methods
[i
] == m
)
551 return &m_methods
[i
];
555 const wxMethod
* wxClass::RecursiveUpwardFindMethod(const wxMethod
& m
,
556 const wxXmlInterface
* allclasses
) const
558 // first, search into *this
559 const wxMethod
* ret
= FindMethod(m
);
563 // then, search into its parents
564 for (unsigned int i
=0; i
<m_parents
.GetCount(); i
++)
566 // ignore non-wx-classes parents
567 // AD-HOC FIX: discard wxScrolledT_Helper parent as it always gives errors
568 if (m_parents
[i
].StartsWith("wx") && m_parents
[i
] != "wxScrolledT_Helper")
570 const wxClass
*parent
= allclasses
->FindClass(m_parents
[i
]);
572 wxLogError("Could not find parent '%s' of class '%s'...",
573 m_parents
[i
], GetName());
577 const wxMethod
*parentMethod
= parent
->RecursiveUpwardFindMethod(m
, allclasses
);
583 // could not find anything even in parent classes...
587 wxMethodPtrArray
wxClass::FindMethodsNamed(const wxString
& name
) const
589 wxMethodPtrArray ret
;
591 for (unsigned int i
=0; i
<m_methods
.GetCount(); i
++)
592 if (m_methods
[i
].GetName() == name
)
593 ret
.Add(&m_methods
[i
]);
599 wxMethodPtrArray
wxClass::RecursiveUpwardFindMethodsNamed(const wxString
& name
,
600 const wxXmlInterface
* allclasses
) const
602 // first, search into *this
603 wxMethodPtrArray ret
= FindMethodsNamed(name
);
604 if (ret
.GetCount()>0)
605 return ret
; // stop here, don't look upward in the parents
607 // then, search into parents of this class
608 for (unsigned int i
=0; i
<m_parents
.GetCount(); i
++)
610 // AD-HOC FIX: discard wxScrolledT_Helper parent as it always gives errors
611 if (m_parents
[i
].StartsWith("wx") && m_parents
[i
] != "wxScrolledT_Helper")
613 const wxClass
*parent
= allclasses
->FindClass(m_parents
[i
]);
615 wxLogError("Could not find parent '%s' of class '%s'...",
616 m_parents
[i
], GetName());
620 wxMethodPtrArray temp
= parent
->RecursiveUpwardFindMethodsNamed(name
, allclasses
);
621 WX_APPEND_ARRAY(ret
, temp
);
630 // ----------------------------------------------------------------------------
632 // ----------------------------------------------------------------------------
634 WX_DEFINE_SORTED_ARRAY(wxClass
*, wxSortedClassArray
);
636 int CompareWxClassObjects(wxClass
*item1
, wxClass
*item2
)
638 // sort alphabetically
639 return item1
->GetName().Cmp(item2
->GetName());
642 void wxXmlInterface::Dump(const wxString
& filename
)
644 wxFFileOutputStream
apioutput( filename
);
645 wxTextOutputStream
apiout( apioutput
);
647 // dump the classes in alphabetical order
648 wxSortedClassArray
sorted(CompareWxClassObjects
);
649 sorted
.Alloc(m_classes
.GetCount());
652 for (i
=0; i
<m_classes
.GetCount(); i
++)
653 sorted
.Add(&m_classes
[i
]);
655 // now they have been sorted
656 for (i
=0; i
<sorted
.GetCount(); i
++)
657 sorted
[i
]->Dump(apiout
);
660 bool wxXmlInterface::CheckConsistency() const
662 // this check can be quite slow, so do it only for debug releases:
664 for (unsigned int i
=0; i
<m_classes
.GetCount(); i
++)
666 if (!m_classes
[i
].CheckConsistency())
669 for (unsigned int j
=0; j
<m_classes
.GetCount(); j
++)
670 if (i
!=j
&& m_classes
[i
].GetName() == m_classes
[j
].GetName())
672 wxLogError("two classes have the same name: %s",
673 m_classes
[i
].GetName());
682 wxClassPtrArray
wxXmlInterface::FindClassesDefinedIn(const wxString
& headerfile
) const
686 for (unsigned int i
=0; i
<m_classes
.GetCount(); i
++)
687 if (m_classes
[i
].GetHeader() == headerfile
)
688 ret
.Add(&m_classes
[i
]);
694 // ----------------------------------------------------------------------------
695 // wxXmlGccInterface helper declarations
696 // ----------------------------------------------------------------------------
698 // or-able flags for a toResolveTypeItem->attrib:
699 #define ATTRIB_CONST 1
700 #define ATTRIB_REFERENCE 2
701 #define ATTRIB_POINTER 4
702 #define ATTRIB_ARRAY 8
704 // it may sound strange but gccxml, in order to produce shorter ID names
705 // uses (after the underscore) characters in range 0-9 and a-z in the ID names;
706 // in order to be able to translate such strings into numbers using strtoul()
707 // we use as base 10 (possible digits) + 25 (possible characters) = 35
708 #define GCCXML_BASE 35
710 class toResolveTypeItem
713 toResolveTypeItem() { attribs
=0; }
714 toResolveTypeItem(unsigned int refID
, unsigned int attribint
)
715 : ref(refID
), attribs(attribint
) {}
717 unsigned long ref
, // the referenced type's ID
718 attribs
; // the attributes of this reference
723 // for wxToResolveTypeHashMap, keys == gccXML IDs and values == toResolveTypeItem
724 WX_DECLARE_HASH_MAP( unsigned long, toResolveTypeItem
,
725 wxIntegerHash
, wxIntegerEqual
,
726 wxToResolveTypeHashMap
);
728 // for wxClassMemberIdHashMap, keys == gccXML IDs and values == wxClass which owns that member ID
729 WX_DECLARE_HASH_MAP( unsigned long, wxClass
*,
730 wxIntegerHash
, wxIntegerEqual
,
731 wxClassMemberIdHashMap
);
735 typedef std::map
<unsigned long, toResolveTypeItem
> wxToResolveTypeHashMap
;
739 // utility to parse gccXML ID values;
740 // this function is equivalent to wxString(str).Mid(1).ToULong(&id, GCCXML_BASE)
741 // but is a little bit faster
742 bool getID(unsigned long *id
, const wxString
& str
)
744 const wxStringCharType
* const start
= str
.wx_str()+1;
745 wxStringCharType
*end
;
746 #if wxUSE_UNICODE_WCHAR
747 unsigned long val
= wcstoul(start
, &end
, GCCXML_BASE
);
749 unsigned long val
= strtoul(start
, &end
, GCCXML_BASE
);
752 // return true only if scan was stopped by the terminating NUL and
753 // if the string was not empty to start with and no under/overflow
755 if ( *end
!= '\0' || end
== start
|| errno
== ERANGE
|| errno
== EINVAL
)
762 // utility specialized to parse efficiently the gccXML list of IDs which occur
763 // in nodes like <Class> ones... i.e. numeric values separed by " _" token
764 bool getMemberIDs(wxClassMemberIdHashMap
* map
, wxClass
* p
, const wxString
& str
)
766 const wxStringCharType
* const start
= str
.wx_str();
767 #if wxUSE_UNICODE_WCHAR
768 size_t len
= wcslen(start
);
770 size_t len
= strlen(start
);
773 if (len
== 0 || start
[0] != '_')
776 const wxStringCharType
*curpos
= start
,
778 wxStringCharType
*nexttoken
;
782 // curpos always points to the underscore of the next token to parse:
783 #if wxUSE_UNICODE_WCHAR
784 unsigned long id
= wcstoul(curpos
+1, &nexttoken
, GCCXML_BASE
);
786 unsigned long id
= strtoul(curpos
+1, &nexttoken
, GCCXML_BASE
);
788 if ( *nexttoken
!= ' ' || errno
== ERANGE
|| errno
== EINVAL
)
791 // advance current position
792 curpos
= nexttoken
+ 1;
794 // add this ID to the hashmap
795 wxClassMemberIdHashMap::value_type
v(id
, p
);
803 // ----------------------------------------------------------------------------
805 // ----------------------------------------------------------------------------
807 bool wxXmlGccInterface::Parse(const wxString
& filename
)
813 wxLogMessage("Parsing %s...", filename
);
815 if (!doc
.Load(filename
)) {
816 wxLogError("can't load %s", filename
);
820 // start processing the XML file
821 if (doc
.GetRoot()->GetName() != "GCC_XML") {
822 wxLogError("invalid root node for %s", filename
);
826 wxString version
= doc
.GetRoot()->GetAttribute("cvs_revision");
829 #define MIN_REVISION 120
831 if (!version
.StartsWith("1."))
835 unsigned long rev
= 0;
836 if (!version
.Mid(2).ToULong(&rev
))
839 if (rev
< MIN_REVISION
)
845 wxLogError("The version of GCC-XML used for the creation of %s is too old; "
846 "the cvs_revision attribute of the root node reports '%s', "
847 "minimal required is 1.%d.", filename
, version
, MIN_REVISION
);
851 wxToResolveTypeHashMap toResolveTypes
;
852 wxClassMemberIdHashMap members
;
853 wxTypeIdHashMap types
;
854 wxTypeIdHashMap files
;
855 wxTypeIdHashMap typedefs
;
857 // prealloc quite a lot of memory!
858 m_classes
.Alloc(ESTIMATED_NUM_CLASSES
);
860 // build a list of wx classes and in general of all existent types
861 child
= doc
.GetRoot()->GetChildren();
864 const wxString
& n
= child
->GetName();
866 unsigned long id
= 0;
867 if (!getID(&id
, child
->GetAttribute("id")) || (id
== 0 && n
!= "File")) {
869 // NOTE: <File> nodes can have an id == "f0"...
871 wxLogError("Invalid id for node %s: %s", n
, child
->GetAttribute("id"));
877 wxString cname
= child
->GetAttribute("name");
878 if (cname
.IsEmpty()) {
879 wxLogError("Invalid empty name for '%s' node", n
);
883 // only register wx classes (do remember also the IDs of their members)
884 if (cname
.StartsWith("wx"))
886 // NB: "file" attribute contains an ID value that we'll resolve later
887 m_classes
.Add(wxClass(cname
, child
->GetAttribute("file")));
889 // the just-inserted class:
890 wxClass
*newClass
= &m_classes
.Last();
892 // now get a list of the base classes:
893 wxXmlNode
*baseNode
= child
->GetChildren();
896 // for now we store as "parents" only the parent IDs...
897 // later we will resolve them into full class names
898 if (baseNode
->GetName() == "Base")
899 newClass
->AddParent(baseNode
->GetAttribute("type"));
901 baseNode
= baseNode
->GetNext();
904 const wxString
& ids
= child
->GetAttribute("members");
907 if (child
->GetAttribute("incomplete") != "1") {
908 wxLogError("Invalid member IDs for '%s' class node: %s",
909 cname
, child
->GetAttribute("id"));
912 //else: don't warn the user; it looks like "incomplete" classes
913 // never have any member...
917 // decode the non-empty list of IDs:
918 if (!getMemberIDs(&members
, newClass
, ids
)) {
919 wxLogError("Invalid member IDs for '%s' class node: %s",
920 cname
, child
->GetAttribute("id"));
926 // register this class also as possible return/argument type:
929 else if (n
== "Typedef")
931 unsigned long typeId
= 0;
932 if (!getID(&typeId
, child
->GetAttribute("type"))) {
933 wxLogError("Invalid type for node %s: %s", n
, child
->GetAttribute("type"));
937 // this typedef node tell us that every type referenced with the
938 // "typeId" ID should be called with another name:
939 wxString name
= child
->GetAttribute("name");
941 // save this typedef in a separate hashmap...
942 typedefs
[typeId
] = name
;
946 else if (n
== "PointerType" || n
== "ReferenceType" ||
947 n
== "CvQualifiedType" || n
== "ArrayType")
949 unsigned long type
= 0;
950 if (!getID(&type
, child
->GetAttribute("type")) || type
== 0) {
951 wxLogError("Invalid type for node %s: %s", n
, child
->GetAttribute("type"));
955 unsigned long attr
= 0;
956 if (n
== "PointerType")
957 attr
= ATTRIB_POINTER
;
958 else if (n
== "ReferenceType")
959 attr
= ATTRIB_REFERENCE
;
960 else if (n
== "CvQualifiedType" && child
->GetAttribute("const") == "1")
962 else if (n
== "ArrayType")
965 // these nodes make reference to other types... we'll resolve them later
966 toResolveTypes
[id
] = toResolveTypeItem(type
, attr
);
968 else if (n
== "FunctionType" || n
== "MethodType")
971 TODO: parsing FunctionType and MethodType nodes is not as easy
972 as for other "simple" types.
976 wxXmlNode
*arg
= child
->GetChildren();
979 if (arg
->GetName() == "Argument")
980 argstr
+= arg
->GetAttribute("type") + ", ";
981 arg
= arg
->GetNext();
984 if (argstr
.Len() > 0)
985 argstr
= argstr
.Left(argstr
.Len()-2); // remove final comma
987 // these nodes make reference to other types... we'll resolve them later
988 //toResolveTypes[id] = toResolveTypeItem(ret, 0);
989 //types[id] = child->GetAttribute("returns") + "(" + argstr + ")";
991 types
[id
] = "TOFIX"; // typically this type will be "fixed" thanks
992 // to a typedef later...
994 else if (n
== "File")
996 if (!child
->GetAttribute("id").StartsWith("f")) {
997 wxLogError("Unexpected file ID: %s", child
->GetAttribute("id"));
1001 // just ignore this node... all file IDs/names were already parsed
1002 files
[id
] = child
->GetAttribute("name");
1006 // we register everything else as a possible return/argument type:
1007 const wxString
& name
= child
->GetAttribute("name");
1009 if (!name
.IsEmpty())
1012 //typeNames.Add(name);
1017 // this may happen with unnamed structs/union, special ctors,
1018 // or other exotic things which we are not interested to, since
1019 // they're never used as return/argument types by wxWidgets methods
1023 wxLogWarning("Type node '%s' with ID '%s' does not have name attribute",
1024 n
, child
->GetAttribute("id"));
1027 types
[id
] = "TOFIX";
1031 child
= child
->GetNext();
1033 // give feedback to the user about the progress...
1034 if ((++nodes%PROGRESS_RATE
)==0) ShowProgress();
1037 // some nodes with IDs referenced by methods as return/argument types, do reference
1038 // in turn other nodes (see PointerType, ReferenceType and CvQualifierType above);
1039 // thus we need to resolve their name iteratively:
1040 while (toResolveTypes
.size()>0)
1044 wxLogMessage("%d types were collected; %d types need yet to be resolved...",
1045 types
.size(), toResolveTypes
.size());
1048 for (wxToResolveTypeHashMap::iterator i
= toResolveTypes
.begin();
1049 i
!= toResolveTypes
.end();)
1051 unsigned long id
= i
->first
;
1052 unsigned long referenced
= i
->second
.ref
;
1054 wxTypeIdHashMap::iterator primary
= types
.find(referenced
);
1055 if (primary
!= types
.end())
1057 // this to-resolve-type references a "primary" type
1059 wxString newtype
= primary
->second
;
1060 int attribs
= i
->second
.attribs
;
1062 // attribs may contain a combination of ATTRIB_* flags:
1063 if (attribs
& ATTRIB_CONST
)
1064 newtype
= "const " + newtype
;
1065 if (attribs
& ATTRIB_REFERENCE
)
1066 newtype
= newtype
+ "&";
1067 if (attribs
& ATTRIB_POINTER
)
1068 newtype
= newtype
+ "*";
1069 if (attribs
& ATTRIB_ARRAY
)
1070 newtype
= newtype
+ "[]";
1072 // add the resolved type to the list of "primary" types
1073 if (newtype
.Contains("TOFIX") && typedefs
[id
] != "")
1074 types
[id
] = typedefs
[id
]; // better use a typedef for this type!
1076 types
[id
] = newtype
;
1078 // this one has been resolved; erase it through its iterator!
1079 toResolveTypes
.erase(i
);
1081 // now iterator i is invalid; assign it again to the beginning
1082 i
= toResolveTypes
.begin();
1086 // then search in the referenced types themselves:
1087 wxToResolveTypeHashMap::iterator idx2
= toResolveTypes
.find(referenced
);
1088 if (idx2
!= toResolveTypes
.end())
1090 // merge this to-resolve-type with the idx2->second type
1091 i
->second
.ref
= idx2
->second
.ref
;
1092 i
->second
.attribs
|= idx2
->second
.attribs
;
1094 // this type will eventually be solved in the next while() iteration
1099 wxLogError("Cannot solve '%d' reference type!", referenced
);
1106 // resolve header names
1108 for (i
=0; i
<m_classes
.GetCount(); i
++)
1110 unsigned long fileID
= 0;
1111 if (!getID(&fileID
, m_classes
[i
].GetHeader()) || fileID
== 0) {
1112 wxLogError("invalid header id: %s", m_classes
[i
].GetHeader());
1117 wxTypeIdHashMap::const_iterator idx
= files
.find(fileID
);
1118 if (idx
== files
.end())
1120 // this is an error!
1121 wxLogError("couldn't find file ID '%s'", m_classes
[i
].GetHeader());
1124 m_classes
[i
].SetHeader(idx
->second
);
1127 // resolve parent names
1128 for (i
=0; i
<m_classes
.GetCount(); i
++)
1130 for (unsigned int k
=0; k
<m_classes
[i
].GetParentCount(); k
++)
1134 if (!getID(&id
, m_classes
[i
].GetParent(k
))) {
1135 wxLogError("invalid parent class ID for '%s'", m_classes
[i
].GetName());
1139 wxTypeIdHashMap::const_iterator idx
= types
.find(id
);
1140 if (idx
== types
.end())
1142 // this is an error!
1143 wxLogError("couldn't find parent class ID '%d'", id
);
1146 // replace k-th parent with its true name:
1147 m_classes
[i
].SetParent(k
, idx
->second
);
1151 // build the list of the wx methods
1152 child
= doc
.GetRoot()->GetChildren();
1155 wxString n
= child
->GetName(), acc
= child
->GetAttribute("access");
1157 // only register public&protected methods
1158 if ((acc
== "public" || acc
== "protected") &&
1159 (n
== "Method" || n
== "Constructor" || n
== "Destructor" || n
== "OperatorMethod"))
1161 unsigned long id
= 0;
1162 if (!getID(&id
, child
->GetAttribute("id"))) {
1163 wxLogError("invalid ID for node '%s' with ID '%s'", n
, child
->GetAttribute("id"));
1167 wxClassMemberIdHashMap::const_iterator it
= members
.find(id
);
1168 if (it
!= members
.end())
1170 wxClass
*p
= it
->second
;
1172 // this <Method> node is a method of the i-th class!
1174 if (!ParseMethod(child
, types
, newfunc
)) {
1175 wxLogError("The method '%s' could not be added to class '%s'",
1176 child
->GetAttribute("demangled"), p
->GetName());
1180 // do some additional check that we can do only here:
1182 if (newfunc
.IsCtor() && !p
->IsValidCtorForThisClass(newfunc
)) {
1183 wxLogError("The method '%s' does not seem to be a ctor for '%s'",
1184 newfunc
.GetName(), p
->GetName());
1187 if (newfunc
.IsDtor() && !p
->IsValidDtorForThisClass(newfunc
)) {
1188 wxLogError("The method '%s' does not seem to be a dtor for '%s'",
1189 newfunc
.GetName(), p
->GetName());
1193 p
->AddMethod(newfunc
);
1197 child
= child
->GetNext();
1199 // give feedback to the user about the progress...
1200 if ((++nodes%PROGRESS_RATE
)==0) ShowProgress();
1203 if (!CheckConsistency())
1204 return false; // the check failed
1209 bool wxXmlGccInterface::ParseMethod(const wxXmlNode
*p
,
1210 const wxTypeIdHashMap
& types
,
1213 // get the real name
1214 wxString name
= p
->GetAttribute("name").Strip(wxString::both
);
1215 if (p
->GetName() == "Destructor")
1217 else if (p
->GetName() == "OperatorMethod")
1218 name
= "operator" + name
;
1220 // resolve return type
1222 unsigned long retid
= 0;
1223 if (!getID(&retid
, p
->GetAttribute("returns")) || retid
== 0)
1225 if (p
->GetName() != "Destructor" && p
->GetName() != "Constructor") {
1226 wxLogError("Empty return ID for method '%s', with ID '%s'",
1227 name
, p
->GetAttribute("id"));
1233 wxTypeIdHashMap::const_iterator retidx
= types
.find(retid
);
1234 if (retidx
== types
.end()) {
1235 wxLogError("Could not find return type ID '%s'", retid
);
1239 ret
= wxType(retidx
->second
);
1241 wxLogError("Invalid return type '%s' for method '%s', with ID '%s'",
1242 retidx
->second
, name
, p
->GetAttribute("id"));
1247 // resolve argument types
1248 wxArgumentTypeArray argtypes
;
1249 wxXmlNode
*arg
= p
->GetChildren();
1252 if (arg
->GetName() == "Argument")
1254 unsigned long id
= 0;
1255 if (!getID(&id
, arg
->GetAttribute("type")) || id
== 0) {
1256 wxLogError("Invalid argument type ID '%s' for method '%s' with ID %s",
1257 arg
->GetAttribute("type"), name
, p
->GetAttribute("id"));
1261 wxTypeIdHashMap::const_iterator idx
= types
.find(id
);
1262 if (idx
== types
.end()) {
1263 wxLogError("Could not find argument type ID '%s'", id
);
1267 argtypes
.Add(wxArgumentType(idx
->second
,
1268 arg
->GetAttribute("default"),
1269 arg
->GetAttribute("name")));
1272 arg
= arg
->GetNext();
1275 m
.SetReturnType(ret
);
1277 m
.SetArgumentTypes(argtypes
);
1278 m
.SetConst(p
->GetAttribute("const") == "1");
1279 m
.SetStatic(p
->GetAttribute("static") == "1");
1281 // NOTE: gccxml is smart enough to mark as virtual those functions
1282 // which are declared virtual in base classes but don't have
1283 // the "virtual" keyword explicitely indicated in the derived
1284 // classes... so we don't need any further logic for virtuals
1286 m
.SetVirtual(p
->GetAttribute("virtual") == "1");
1287 m
.SetPureVirtual(p
->GetAttribute("pure_virtual") == "1");
1288 m
.SetDeprecated(p
->GetAttribute("attributes") == "deprecated");
1290 // decode access specifier
1291 if (p
->GetAttribute("access") == "public")
1292 m
.SetAccessSpecifier(wxMAS_PUBLIC
);
1293 else if (p
->GetAttribute("access") == "protected")
1294 m
.SetAccessSpecifier(wxMAS_PROTECTED
);
1295 else if (p
->GetAttribute("access") == "private")
1296 m
.SetAccessSpecifier(wxMAS_PRIVATE
);
1299 wxLogError("The prototype '%s' is not valid!", m
.GetAsString());
1308 // ----------------------------------------------------------------------------
1309 // wxXmlDoxygenInterface global helpers
1310 // ----------------------------------------------------------------------------
1312 static wxString
GetTextFromChildren(const wxXmlNode
*n
)
1316 // consider the tree
1318 // <a><b>this</b> is a <b>string</b></a>
1327 // unlike wxXmlNode::GetNodeContent() which would return " is a "
1328 // this function returns "this is a string"
1330 wxXmlNode
*ref
= n
->GetChildren();
1332 if (ref
->GetType() == wxXML_ELEMENT_NODE
)
1333 text
+= ref
->GetNodeContent();
1334 else if (ref
->GetType() == wxXML_TEXT_NODE
)
1335 text
+= ref
->GetContent();
1337 wxLogWarning("Unexpected node type while getting text from '%s' node", n
->GetName());
1339 ref
= ref
->GetNext();
1345 static bool HasTextNodeContaining(const wxXmlNode
*parent
, const wxString
& name
)
1350 wxXmlNode
*p
= parent
->GetChildren();
1353 switch (p
->GetType())
1355 case wxXML_TEXT_NODE
:
1356 if (p
->GetContent() == name
)
1360 case wxXML_ELEMENT_NODE
:
1361 // recurse into this node...
1362 if (HasTextNodeContaining(p
, name
))
1377 static const wxXmlNode
* FindNodeNamed(const wxXmlNode
* parent
, const wxString
& name
)
1382 const wxXmlNode
*p
= parent
->GetChildren();
1385 if (p
->GetName() == name
)
1388 // search recursively in the children of this node
1389 const wxXmlNode
*ret
= FindNodeNamed(p
, name
);
1399 int GetAvailabilityFor(const wxXmlNode
*node
)
1401 // identify <onlyfor> custom XML tags
1402 const wxXmlNode
* onlyfor
= FindNodeNamed(node
, "onlyfor");
1404 return wxPORT_UNKNOWN
;
1406 wxArrayString ports
= wxSplit(onlyfor
->GetNodeContent(), ',');
1407 int nAvail
= wxPORT_UNKNOWN
;
1408 for (unsigned int i
=0; i
< ports
.GetCount(); i
++)
1410 if (!ports
[i
].StartsWith("wx")) {
1411 wxLogError("unexpected port ID '%s'", ports
[i
]);
1415 nAvail
|= wxPlatformInfo::GetPortId(ports
[i
].Mid(2));
1422 // ----------------------------------------------------------------------------
1423 // wxXmlDoxygenInterface
1424 // ----------------------------------------------------------------------------
1426 bool wxXmlDoxygenInterface::Parse(const wxString
& filename
)
1428 wxXmlDocument index
;
1429 wxXmlNode
*compound
;
1431 wxLogMessage("Parsing %s...", filename
);
1433 if (!index
.Load(filename
)) {
1434 wxLogError("can't load %s", filename
);
1438 // start processing the index:
1439 if (index
.GetRoot()->GetName() != "doxygenindex") {
1440 wxLogError("invalid root node for %s", filename
);
1445 NB: we may need in future to do a version-check here if the
1446 format of the XML generated by doxygen changes.
1447 For now (doxygen version 1.5.5), this check is not required
1448 since AFAIK the XML format never changed since it was introduced.
1451 m_classes
.Alloc(ESTIMATED_NUM_CLASSES
);
1453 // process files referenced by this index file
1454 compound
= index
.GetRoot()->GetChildren();
1457 if (compound
->GetName() == "compound" &&
1458 compound
->GetAttribute("kind") == "class")
1460 wxString refid
= compound
->GetAttribute("refid");
1462 wxFileName
fn(filename
);
1463 if (!ParseCompoundDefinition(fn
.GetPath(wxPATH_GET_SEPARATOR
) + refid
+ ".xml"))
1467 compound
= compound
->GetNext();
1471 if (!CheckConsistency())
1472 return false; // the check failed
1477 bool wxXmlDoxygenInterface::ParseCompoundDefinition(const wxString
& filename
)
1479 wxClassMemberIdHashMap parents
;
1486 wxLogMessage("Parsing %s...", filename
);
1489 if (!doc
.Load(filename
)) {
1490 wxLogError("can't load %s", filename
);
1494 // start processing this compound definition XML
1495 if (doc
.GetRoot()->GetName() != "doxygen") {
1496 wxLogError("invalid root node for %s", filename
);
1500 // build a list of wx classes
1501 child
= doc
.GetRoot()->GetChildren();
1504 if (child
->GetName() == "compounddef" &&
1505 child
->GetAttribute("kind") == "class")
1509 wxString absoluteFile
, header
;
1511 wxXmlNode
*subchild
= child
->GetChildren();
1514 // NOTE: when documenting functions using the //@{ and //@}
1515 // tags to create function groups, doxygen puts the
1516 // contained methods into a "user-defined" section
1517 // so we _must_ use the "prot" attribute to distinguish
1518 // public/protected methods from private ones and cannot
1519 // rely on the kind="public" attribute of <sectiondef>
1520 if (subchild
->GetName() == "sectiondef")
1522 wxXmlNode
*membernode
= subchild
->GetChildren();
1525 const wxString
& accessSpec
= membernode
->GetAttribute("prot");
1527 // parse only public&protected functions:
1528 if (membernode
->GetName() == "memberdef" &&
1529 membernode
->GetAttribute("kind") == "function" &&
1530 (accessSpec
== "public" || accessSpec
== "protected"))
1534 if (!ParseMethod(membernode
, m
, header
)) {
1535 wxLogError("The method '%s' could not be added to class '%s'",
1536 m
.GetName(), klass
.GetName());
1540 if (accessSpec
== "public")
1541 m
.SetAccessSpecifier(wxMAS_PUBLIC
);
1542 else if (accessSpec
== "protected")
1543 m
.SetAccessSpecifier(wxMAS_PROTECTED
);
1544 else if (accessSpec
== "private")
1545 m
.SetAccessSpecifier(wxMAS_PRIVATE
);
1547 if (absoluteFile
.IsEmpty())
1548 absoluteFile
= header
;
1549 else if (header
!= absoluteFile
)
1551 wxLogError("The method '%s' is documented in a different "
1552 "file from others (which belong to '%s') ?",
1553 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 explicitely 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());