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")))
237 wxLogMessage("Supposing '%s' default value to be the same of '%s'...",
238 m_strDefaultValueForCmp
, m
.m_strDefaultValueForCmp
);
243 else if (m_strTypeClean
== "float" || m_strTypeClean
== "double")
244 // gccXML translates the default floating values in a hardly usable
245 // format; e.g. 25.2 => 2.51999999999999992894572642398998141288757324219e+1
246 // we avoid check on these...
249 if (m_strDefaultValueForCmp
!= m
.m_strDefaultValueForCmp
)
251 // maybe the default values are numbers.
252 // in this case gccXML gives as default values things like '-0x0000001' instead of just '-1'.
253 // To handle these cases, we try to convert the default value strings to numbers:
254 long def1val
, def2val
;
255 if (m_strDefaultValueForCmp
.ToLong(&def1val
, 0 /* auto-detect */) &&
256 m
.m_strDefaultValueForCmp
.ToLong(&def2val
, 0 /* auto-detect */))
258 if (def1val
== def2val
)
259 return true; // the default values match
263 wxLogMessage("Argument type '%s = %s' has different default value from '%s = %s'",
264 m_strType
, m_strDefaultValueForCmp
, m
.m_strType
, m
.m_strDefaultValueForCmp
);
268 // we deliberately avoid checks on the argument name
274 // ----------------------------------------------------------------------------
276 // ----------------------------------------------------------------------------
278 bool wxMethod::IsOk() const
280 // NOTE: m_retType can be a wxEmptyType, and means that this method
281 // is a ctor or a dtor.
282 if (!m_retType
.IsOk() && m_retType
!=wxEmptyType
) {
283 wxLogError("'%s' method has invalid return type: %s", m_retType
.GetAsString());
287 if (m_strName
.IsEmpty())
290 // a function can't be both const and static or virtual and static!
291 if ((m_bConst
&& m_bStatic
) || ((m_bVirtual
|| m_bPureVirtual
) && m_bStatic
)) {
292 wxLogError("'%s' method can't be both const/static or virtual/static", m_strName
);
296 wxASSERT(!m_bPureVirtual
|| (m_bPureVirtual
&& m_bVirtual
));
298 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
299 if (!m_args
[i
].IsOk()) {
300 wxLogError("'%s' method has invalid %d-th argument type: %s",
301 m_strName
, i
+1, m_args
[i
].GetAsString());
305 // NB: the default value of the arguments can contain pretty much everything
306 // (think to e.g. wxPoint(3+4/2,0) or *wxBLACK or someClass<type>)
307 // so we don't do any test on their contents
308 if (m_args
.GetCount()>0)
310 bool previousArgHasDefault
= m_args
[0].HasDefaultValue();
311 for (unsigned int i
=1; i
<m_args
.GetCount(); i
++)
313 if (previousArgHasDefault
&& !m_args
[i
].HasDefaultValue()) {
314 wxLogError("'%s' method has %d-th argument which has no default value "
315 "(while the previous one had one!)",
320 previousArgHasDefault
= m_args
[i
].HasDefaultValue();
327 bool wxMethod::MatchesExceptForAttributes(const wxMethod
& m
) const
329 if (GetReturnType() != m
.GetReturnType() ||
330 GetName() != m
.GetName())
333 wxLogMessage("The method '%s' does not match method '%s'; different names/rettype", GetName(), m
.GetName());
337 if (m_args
.GetCount()!=m
.m_args
.GetCount()) {
339 wxLogMessage("Method '%s' has %d arguments while '%s' has %d arguments",
340 m_strName
, m_args
.GetCount(), m_strName
, m
.m_args
.GetCount());
344 // compare argument types
345 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
346 if (m_args
[i
] != m
.m_args
[i
])
352 bool wxMethod::ActsAsDefaultCtor() const
357 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
358 if (!m_args
[i
].HasDefaultValue())
364 bool wxMethod::operator==(const wxMethod
& m
) const
367 if (IsConst() != m
.IsConst() ||
368 IsStatic() != m
.IsStatic() ||
369 IsVirtual() != m
.IsVirtual() ||
370 IsPureVirtual() != m
.IsPureVirtual() ||
371 IsDeprecated() != m
.IsDeprecated() ||
372 GetAccessSpecifier() != m
.GetAccessSpecifier())
375 wxLogMessage("The method '%s' does not match method '%s'; different attributes", GetName(), m
.GetName());
380 // check everything else
381 return MatchesExceptForAttributes(m
);
384 wxString
wxMethod::GetAsString(bool bWithArgumentNames
, bool bCleanDefaultValues
,
385 bool bDeprecated
, bool bAccessSpec
) const
389 // NOTE: for return and argument types, never use wxType::GetAsCleanString
390 // since in that way we'd miss important decorators like &,*,const etc
392 if (m_retType
!=wxEmptyType
)
393 ret
+= m_retType
.GetAsString() + " ";
394 //else; this is a ctor or dtor
396 ret
+= m_strName
+ "(";
398 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
400 ret
+= m_args
[i
].GetAsString();
402 const wxString
& name
= m_args
[i
].GetArgumentName();
403 if (bWithArgumentNames
&& !name
.IsEmpty())
406 const wxString
& def
= bCleanDefaultValues
?
407 m_args
[i
].GetDefaultCleanValue() : m_args
[i
].GetDefaultValue();
414 if (m_args
.GetCount()>0)
415 ret
= ret
.Left(ret
.Len()-2);
422 ret
= "static " + ret
;
423 if (m_bVirtual
|| m_bPureVirtual
)
424 ret
= "virtual " + ret
;
427 if (m_bDeprecated
&& bDeprecated
)
428 ret
+= " [deprecated]";
437 case wxMAS_PROTECTED
:
438 ret
+= " [protected]";
449 void wxMethod::Dump(wxTextOutputStream
& stream
) const
451 stream
<< "[" + m_retType
.GetAsString() + "]";
452 stream
<< "[" + m_strName
+ "]";
454 for (unsigned int i
=0; i
<m_args
.GetCount(); i
++)
455 stream
<< "[" + m_args
[i
].GetAsString() + " " + m_args
[i
].GetArgumentName() +
456 "=" + m_args
[i
].GetDefaultValue() + "]";
463 stream
<< " VIRTUAL";
465 stream
<< " PURE-VIRTUAL";
467 stream
<< " DEPRECATED";
472 // ----------------------------------------------------------------------------
474 // ----------------------------------------------------------------------------
476 wxString
wxClass::GetNameWithoutTemplate() const
478 // NB: I'm not sure this is the right terminology for this function!
480 if (m_strName
.Contains("<"))
481 return m_strName
.Left(m_strName
.Find("<"));
485 bool wxClass::IsValidCtorForThisClass(const wxMethod
& m
) const
487 // remember that e.g. the ctor for wxWritableCharTypeBuffer<wchar_t> is
488 // named wxWritableCharTypeBuffer, without the <...> part!
490 if (m
.IsCtor() && m
.GetName() == GetNameWithoutTemplate())
496 bool wxClass::IsValidDtorForThisClass(const wxMethod
& m
) const
498 if (m
.IsDtor() && m
.GetName() == "~" + GetNameWithoutTemplate())
504 void wxClass::Dump(wxTextOutputStream
& out
) const
506 out
<< m_strName
+ "\n";
508 for (unsigned int i
=0; i
<m_methods
.GetCount(); i
++) {
510 // dump all our methods
512 m_methods
[i
].Dump(out
);
519 bool wxClass::CheckConsistency() const
521 for (unsigned int i
=0; i
<m_methods
.GetCount(); i
++)
522 for (unsigned int j
=0; j
<m_methods
.GetCount(); j
++)
523 if (i
!=j
&& m_methods
[i
] == m_methods
[j
])
525 wxLogError("class %s has two methods with the same prototype: '%s'",
526 m_strName
, m_methods
[i
].GetAsString());
530 //((wxClass*)this)->m_methods.RemoveAt(j);
537 const wxMethod
* wxClass::FindMethod(const wxMethod
& m
) const
539 for (unsigned int i
=0; i
<m_methods
.GetCount(); i
++)
540 if (m_methods
[i
] == m
)
541 return &m_methods
[i
];
545 const wxMethod
* wxClass::RecursiveUpwardFindMethod(const wxMethod
& m
,
546 const wxXmlInterface
* allclasses
) const
548 // first, search into *this
549 const wxMethod
* ret
= FindMethod(m
);
553 // then, search into its parents
554 for (unsigned int i
=0; i
<m_parents
.GetCount(); i
++)
556 // ignore non-wx-classes parents
557 // AD-HOC FIX: discard wxScrolledT_Helper parent as it always gives errors
558 if (m_parents
[i
].StartsWith("wx") && m_parents
[i
] != "wxScrolledT_Helper")
560 const wxClass
*parent
= allclasses
->FindClass(m_parents
[i
]);
562 wxLogError("Could not find parent '%s' of class '%s'...",
563 m_parents
[i
], GetName());
567 const wxMethod
*parentMethod
= parent
->RecursiveUpwardFindMethod(m
, allclasses
);
573 // could not find anything even in parent classes...
577 wxMethodPtrArray
wxClass::FindMethodsNamed(const wxString
& name
) const
579 wxMethodPtrArray ret
;
581 for (unsigned int i
=0; i
<m_methods
.GetCount(); i
++)
582 if (m_methods
[i
].GetName() == name
)
583 ret
.Add(&m_methods
[i
]);
589 wxMethodPtrArray
wxClass::RecursiveUpwardFindMethodsNamed(const wxString
& name
,
590 const wxXmlInterface
* allclasses
) const
592 // first, search into *this
593 wxMethodPtrArray ret
= FindMethodsNamed(name
);
594 if (ret
.GetCount()>0)
595 return ret
; // stop here, don't look upward in the parents
597 // then, search into parents of this class
598 for (unsigned int i
=0; i
<m_parents
.GetCount(); i
++)
600 // AD-HOC FIX: discard wxScrolledT_Helper parent as it always gives errors
601 if (m_parents
[i
].StartsWith("wx") && m_parents
[i
] != "wxScrolledT_Helper")
603 const wxClass
*parent
= allclasses
->FindClass(m_parents
[i
]);
605 wxLogError("Could not find parent '%s' of class '%s'...",
606 m_parents
[i
], GetName());
610 wxMethodPtrArray temp
= parent
->RecursiveUpwardFindMethodsNamed(name
, allclasses
);
611 WX_APPEND_ARRAY(ret
, temp
);
620 // ----------------------------------------------------------------------------
622 // ----------------------------------------------------------------------------
624 WX_DEFINE_SORTED_ARRAY(wxClass
*, wxSortedClassArray
);
626 int CompareWxClassObjects(wxClass
*item1
, wxClass
*item2
)
628 // sort alphabetically
629 return item1
->GetName().Cmp(item2
->GetName());
632 void wxXmlInterface::Dump(const wxString
& filename
)
634 wxFFileOutputStream
apioutput( filename
);
635 wxTextOutputStream
apiout( apioutput
);
637 // dump the classes in alphabetical order
638 wxSortedClassArray
sorted(CompareWxClassObjects
);
639 sorted
.Alloc(m_classes
.GetCount());
642 for (i
=0; i
<m_classes
.GetCount(); i
++)
643 sorted
.Add(&m_classes
[i
]);
645 // now they have been sorted
646 for (i
=0; i
<sorted
.GetCount(); i
++)
647 sorted
[i
]->Dump(apiout
);
650 bool wxXmlInterface::CheckConsistency() const
652 // this check can be quite slow, so do it only for debug releases:
654 for (unsigned int i
=0; i
<m_classes
.GetCount(); i
++)
656 if (!m_classes
[i
].CheckConsistency())
659 for (unsigned int j
=0; j
<m_classes
.GetCount(); j
++)
660 if (i
!=j
&& m_classes
[i
].GetName() == m_classes
[j
].GetName())
662 wxLogError("two classes have the same name: %s",
663 m_classes
[i
].GetName());
672 wxClassPtrArray
wxXmlInterface::FindClassesDefinedIn(const wxString
& headerfile
) const
676 for (unsigned int i
=0; i
<m_classes
.GetCount(); i
++)
677 if (m_classes
[i
].GetHeader() == headerfile
)
678 ret
.Add(&m_classes
[i
]);
684 // ----------------------------------------------------------------------------
685 // wxXmlGccInterface helper declarations
686 // ----------------------------------------------------------------------------
688 // or-able flags for a toResolveTypeItem->attrib:
689 #define ATTRIB_CONST 1
690 #define ATTRIB_REFERENCE 2
691 #define ATTRIB_POINTER 4
692 #define ATTRIB_ARRAY 8
694 // it may sound strange but gccxml, in order to produce shorter ID names
695 // uses (after the underscore) characters in range 0-9 and a-z in the ID names;
696 // in order to be able to translate such strings into numbers using strtoul()
697 // we use as base 10 (possible digits) + 25 (possible characters) = 35
698 #define GCCXML_BASE 35
700 class toResolveTypeItem
703 toResolveTypeItem() { attribs
=0; }
704 toResolveTypeItem(unsigned int refID
, unsigned int attribint
)
705 : ref(refID
), attribs(attribint
) {}
707 unsigned long ref
, // the referenced type's ID
708 attribs
; // the attributes of this reference
713 // for wxToResolveTypeHashMap, keys == gccXML IDs and values == toResolveTypeItem
714 WX_DECLARE_HASH_MAP( unsigned long, toResolveTypeItem
,
715 wxIntegerHash
, wxIntegerEqual
,
716 wxToResolveTypeHashMap
);
718 // for wxClassMemberIdHashMap, keys == gccXML IDs and values == wxClass which owns that member ID
719 WX_DECLARE_HASH_MAP( unsigned long, wxClass
*,
720 wxIntegerHash
, wxIntegerEqual
,
721 wxClassMemberIdHashMap
);
725 typedef std::map
<unsigned long, toResolveTypeItem
> wxToResolveTypeHashMap
;
729 // utility to parse gccXML ID values;
730 // this function is equivalent to wxString(str).Mid(1).ToULong(&id, GCCXML_BASE)
731 // but is a little bit faster
732 bool getID(unsigned long *id
, const wxString
& str
)
734 const wxStringCharType
* const start
= str
.wx_str()+1;
735 wxStringCharType
*end
;
736 #if wxUSE_UNICODE_WCHAR
737 unsigned long val
= wcstoul(start
, &end
, GCCXML_BASE
);
739 unsigned long val
= strtoul(start
, &end
, GCCXML_BASE
);
742 // return true only if scan was stopped by the terminating NUL and
743 // if the string was not empty to start with and no under/overflow
745 if ( *end
!= '\0' || end
== start
|| errno
== ERANGE
|| errno
== EINVAL
)
752 // utility specialized to parse efficiently the gccXML list of IDs which occur
753 // in nodes like <Class> ones... i.e. numeric values separed by " _" token
754 bool getMemberIDs(wxClassMemberIdHashMap
* map
, wxClass
* p
, const wxString
& str
)
756 const wxStringCharType
* const start
= str
.wx_str();
757 #if wxUSE_UNICODE_WCHAR
758 size_t len
= wcslen(start
);
760 size_t len
= strlen(start
);
763 if (len
== 0 || start
[0] != '_')
766 const wxStringCharType
*curpos
= start
,
768 wxStringCharType
*nexttoken
;
772 // curpos always points to the underscore of the next token to parse:
773 #if wxUSE_UNICODE_WCHAR
774 unsigned long id
= wcstoul(curpos
+1, &nexttoken
, GCCXML_BASE
);
776 unsigned long id
= strtoul(curpos
+1, &nexttoken
, GCCXML_BASE
);
778 if ( *nexttoken
!= ' ' || errno
== ERANGE
|| errno
== EINVAL
)
781 // advance current position
782 curpos
= nexttoken
+ 1;
784 // add this ID to the hashmap
785 wxClassMemberIdHashMap::value_type
v(id
, p
);
793 // ----------------------------------------------------------------------------
795 // ----------------------------------------------------------------------------
797 bool wxXmlGccInterface::Parse(const wxString
& filename
)
803 wxLogMessage("Parsing %s...", filename
);
805 if (!doc
.Load(filename
)) {
806 wxLogError("can't load %s", filename
);
810 // start processing the XML file
811 if (doc
.GetRoot()->GetName() != "GCC_XML") {
812 wxLogError("invalid root node for %s", filename
);
816 wxString version
= doc
.GetRoot()->GetAttribute("cvs_revision");
819 #define MIN_REVISION 120
821 if (!version
.StartsWith("1."))
825 unsigned long rev
= 0;
826 if (!version
.Mid(2).ToULong(&rev
))
829 if (rev
< MIN_REVISION
)
835 wxLogError("The version of GCC-XML used for the creation of %s is too old; "
836 "the cvs_revision attribute of the root node reports '%s', "
837 "minimal required is 1.%d.", filename
, version
, MIN_REVISION
);
841 wxToResolveTypeHashMap toResolveTypes
;
842 wxClassMemberIdHashMap members
;
843 wxTypeIdHashMap types
;
844 wxTypeIdHashMap files
;
845 wxTypeIdHashMap typedefs
;
847 // prealloc quite a lot of memory!
848 m_classes
.Alloc(ESTIMATED_NUM_CLASSES
);
850 // build a list of wx classes and in general of all existent types
851 child
= doc
.GetRoot()->GetChildren();
854 const wxString
& n
= child
->GetName();
856 unsigned long id
= 0;
857 if (!getID(&id
, child
->GetAttribute("id")) || (id
== 0 && n
!= "File")) {
859 // NOTE: <File> nodes can have an id == "f0"...
861 wxLogError("Invalid id for node %s: %s", n
, child
->GetAttribute("id"));
867 wxString cname
= child
->GetAttribute("name");
868 if (cname
.IsEmpty()) {
869 wxLogError("Invalid empty name for '%s' node", n
);
873 // only register wx classes (do remember also the IDs of their members)
874 if (cname
.StartsWith("wx"))
876 // NB: "file" attribute contains an ID value that we'll resolve later
877 m_classes
.Add(wxClass(cname
, child
->GetAttribute("file")));
879 // the just-inserted class:
880 wxClass
*newClass
= &m_classes
.Last();
882 // now get a list of the base classes:
883 wxXmlNode
*baseNode
= child
->GetChildren();
886 // for now we store as "parents" only the parent IDs...
887 // later we will resolve them into full class names
888 if (baseNode
->GetName() == "Base")
889 newClass
->AddParent(baseNode
->GetAttribute("type"));
891 baseNode
= baseNode
->GetNext();
894 const wxString
& ids
= child
->GetAttribute("members");
897 if (child
->GetAttribute("incomplete") != "1") {
898 wxLogError("Invalid member IDs for '%s' class node: %s",
899 cname
, child
->GetAttribute("id"));
902 //else: don't warn the user; it looks like "incomplete" classes
903 // never have any member...
907 // decode the non-empty list of IDs:
908 if (!getMemberIDs(&members
, newClass
, ids
)) {
909 wxLogError("Invalid member IDs for '%s' class node: %s",
910 cname
, child
->GetAttribute("id"));
916 // register this class also as possible return/argument type:
919 else if (n
== "Typedef")
921 unsigned long typeId
= 0;
922 if (!getID(&typeId
, child
->GetAttribute("type"))) {
923 wxLogError("Invalid type for node %s: %s", n
, child
->GetAttribute("type"));
927 // this typedef node tell us that every type referenced with the
928 // "typeId" ID should be called with another name:
929 wxString name
= child
->GetAttribute("name");
931 // save this typedef in a separate hashmap...
932 typedefs
[typeId
] = name
;
936 else if (n
== "PointerType" || n
== "ReferenceType" ||
937 n
== "CvQualifiedType" || n
== "ArrayType")
939 unsigned long type
= 0;
940 if (!getID(&type
, child
->GetAttribute("type")) || type
== 0) {
941 wxLogError("Invalid type for node %s: %s", n
, child
->GetAttribute("type"));
945 unsigned long attr
= 0;
946 if (n
== "PointerType")
947 attr
= ATTRIB_POINTER
;
948 else if (n
== "ReferenceType")
949 attr
= ATTRIB_REFERENCE
;
950 else if (n
== "CvQualifiedType" && child
->GetAttribute("const") == "1")
952 else if (n
== "ArrayType")
955 // these nodes make reference to other types... we'll resolve them later
956 toResolveTypes
[id
] = toResolveTypeItem(type
, attr
);
958 else if (n
== "FunctionType" || n
== "MethodType")
961 TODO: parsing FunctionType and MethodType nodes is not as easy
962 as for other "simple" types.
966 wxXmlNode
*arg
= child
->GetChildren();
969 if (arg
->GetName() == "Argument")
970 argstr
+= arg
->GetAttribute("type") + ", ";
971 arg
= arg
->GetNext();
974 if (argstr
.Len() > 0)
975 argstr
= argstr
.Left(argstr
.Len()-2); // remove final comma
977 // these nodes make reference to other types... we'll resolve them later
978 //toResolveTypes[id] = toResolveTypeItem(ret, 0);
979 //types[id] = child->GetAttribute("returns") + "(" + argstr + ")";
981 types
[id
] = "TOFIX"; // typically this type will be "fixed" thanks
982 // to a typedef later...
984 else if (n
== "File")
986 if (!child
->GetAttribute("id").StartsWith("f")) {
987 wxLogError("Unexpected file ID: %s", child
->GetAttribute("id"));
991 // just ignore this node... all file IDs/names were already parsed
992 files
[id
] = child
->GetAttribute("name");
996 // we register everything else as a possible return/argument type:
997 const wxString
& name
= child
->GetAttribute("name");
1002 //typeNames.Add(name);
1007 // this may happen with unnamed structs/union, special ctors,
1008 // or other exotic things which we are not interested to, since
1009 // they're never used as return/argument types by wxWidgets methods
1012 wxLogWarning("Type node '%s' with ID '%s' does not have name attribute",
1013 n
, child
->GetAttribute("id"));
1015 types
[id
] = "TOFIX";
1019 child
= child
->GetNext();
1021 // give feedback to the user about the progress...
1022 if ((++nodes%PROGRESS_RATE
)==0) ShowProgress();
1025 // some nodes with IDs referenced by methods as return/argument types, do reference
1026 // in turn other nodes (see PointerType, ReferenceType and CvQualifierType above);
1027 // thus we need to resolve their name iteratively:
1028 while (toResolveTypes
.size()>0)
1031 wxLogMessage("%d types were collected; %d types need yet to be resolved...",
1032 types
.size(), toResolveTypes
.size());
1034 for (wxToResolveTypeHashMap::iterator i
= toResolveTypes
.begin();
1035 i
!= toResolveTypes
.end();)
1037 unsigned long id
= i
->first
;
1038 unsigned long referenced
= i
->second
.ref
;
1040 wxTypeIdHashMap::iterator primary
= types
.find(referenced
);
1041 if (primary
!= types
.end())
1043 // this to-resolve-type references a "primary" type
1045 wxString newtype
= primary
->second
;
1046 int attribs
= i
->second
.attribs
;
1048 // attribs may contain a combination of ATTRIB_* flags:
1049 if (attribs
& ATTRIB_CONST
)
1050 newtype
= "const " + newtype
;
1051 if (attribs
& ATTRIB_REFERENCE
)
1052 newtype
= newtype
+ "&";
1053 if (attribs
& ATTRIB_POINTER
)
1054 newtype
= newtype
+ "*";
1055 if (attribs
& ATTRIB_ARRAY
)
1056 newtype
= newtype
+ "[]";
1058 // add the resolved type to the list of "primary" types
1059 if (newtype
.Contains("TOFIX") && typedefs
[id
] != "")
1060 types
[id
] = typedefs
[id
]; // better use a typedef for this type!
1062 types
[id
] = newtype
;
1064 // this one has been resolved; erase it through its iterator!
1065 toResolveTypes
.erase(i
);
1067 // now iterator i is invalid; assign it again to the beginning
1068 i
= toResolveTypes
.begin();
1072 // then search in the referenced types themselves:
1073 wxToResolveTypeHashMap::iterator idx2
= toResolveTypes
.find(referenced
);
1074 if (idx2
!= toResolveTypes
.end())
1076 // merge this to-resolve-type with the idx2->second type
1077 i
->second
.ref
= idx2
->second
.ref
;
1078 i
->second
.attribs
|= idx2
->second
.attribs
;
1080 // this type will eventually be solved in the next while() iteration
1085 wxLogError("Cannot solve '%d' reference type!", referenced
);
1092 // resolve header names
1094 for (i
=0; i
<m_classes
.GetCount(); i
++)
1096 unsigned long fileID
= 0;
1097 if (!getID(&fileID
, m_classes
[i
].GetHeader()) || fileID
== 0) {
1098 wxLogError("invalid header id: %s", m_classes
[i
].GetHeader());
1103 wxTypeIdHashMap::const_iterator idx
= files
.find(fileID
);
1104 if (idx
== files
.end())
1106 // this is an error!
1107 wxLogError("couldn't find file ID '%s'", m_classes
[i
].GetHeader());
1110 m_classes
[i
].SetHeader(idx
->second
);
1113 // resolve parent names
1114 for (i
=0; i
<m_classes
.GetCount(); i
++)
1116 for (unsigned int k
=0; k
<m_classes
[i
].GetParentCount(); k
++)
1120 if (!getID(&id
, m_classes
[i
].GetParent(k
))) {
1121 wxLogError("invalid parent class ID for '%s'", m_classes
[i
].GetName());
1125 wxTypeIdHashMap::const_iterator idx
= types
.find(id
);
1126 if (idx
== types
.end())
1128 // this is an error!
1129 wxLogError("couldn't find parent class ID '%d'", id
);
1132 // replace k-th parent with its true name:
1133 m_classes
[i
].SetParent(k
, idx
->second
);
1137 // build the list of the wx methods
1138 child
= doc
.GetRoot()->GetChildren();
1141 wxString n
= child
->GetName(), acc
= child
->GetAttribute("access");
1143 // only register public&protected methods
1144 if ((acc
== "public" || acc
== "protected") &&
1145 (n
== "Method" || n
== "Constructor" || n
== "Destructor" || n
== "OperatorMethod"))
1147 unsigned long id
= 0;
1148 if (!getID(&id
, child
->GetAttribute("id"))) {
1149 wxLogError("invalid ID for node '%s' with ID '%s'", n
, child
->GetAttribute("id"));
1153 wxClassMemberIdHashMap::const_iterator it
= members
.find(id
);
1154 if (it
!= members
.end())
1156 wxClass
*p
= it
->second
;
1158 // this <Method> node is a method of the i-th class!
1160 if (!ParseMethod(child
, types
, newfunc
)) {
1161 wxLogError("The method '%s' could not be added to class '%s'",
1162 child
->GetAttribute("demangled"), p
->GetName());
1166 // do some additional check that we can do only here:
1168 if (newfunc
.IsCtor() && !p
->IsValidCtorForThisClass(newfunc
)) {
1169 wxLogError("The method '%s' does not seem to be a ctor for '%s'",
1170 newfunc
.GetName(), p
->GetName());
1173 if (newfunc
.IsDtor() && !p
->IsValidDtorForThisClass(newfunc
)) {
1174 wxLogError("The method '%s' does not seem to be a dtor for '%s'",
1175 newfunc
.GetName(), p
->GetName());
1179 p
->AddMethod(newfunc
);
1183 child
= child
->GetNext();
1185 // give feedback to the user about the progress...
1186 if ((++nodes%PROGRESS_RATE
)==0) ShowProgress();
1189 if (!CheckConsistency())
1190 return false; // the check failed
1195 bool wxXmlGccInterface::ParseMethod(const wxXmlNode
*p
,
1196 const wxTypeIdHashMap
& types
,
1199 // get the real name
1200 wxString name
= p
->GetAttribute("name").Strip(wxString::both
);
1201 if (p
->GetName() == "Destructor")
1203 else if (p
->GetName() == "OperatorMethod")
1204 name
= "operator" + name
;
1206 // resolve return type
1208 unsigned long retid
= 0;
1209 if (!getID(&retid
, p
->GetAttribute("returns")) || retid
== 0)
1211 if (p
->GetName() != "Destructor" && p
->GetName() != "Constructor") {
1212 wxLogError("Empty return ID for method '%s', with ID '%s'",
1213 name
, p
->GetAttribute("id"));
1219 wxTypeIdHashMap::const_iterator retidx
= types
.find(retid
);
1220 if (retidx
== types
.end()) {
1221 wxLogError("Could not find return type ID '%s'", retid
);
1225 ret
= wxType(retidx
->second
);
1227 wxLogError("Invalid return type '%s' for method '%s', with ID '%s'",
1228 retidx
->second
, name
, p
->GetAttribute("id"));
1233 // resolve argument types
1234 wxArgumentTypeArray argtypes
;
1235 wxXmlNode
*arg
= p
->GetChildren();
1238 if (arg
->GetName() == "Argument")
1240 unsigned long id
= 0;
1241 if (!getID(&id
, arg
->GetAttribute("type")) || id
== 0) {
1242 wxLogError("Invalid argument type ID '%s' for method '%s' with ID %s",
1243 arg
->GetAttribute("type"), name
, p
->GetAttribute("id"));
1247 wxTypeIdHashMap::const_iterator idx
= types
.find(id
);
1248 if (idx
== types
.end()) {
1249 wxLogError("Could not find argument type ID '%s'", id
);
1253 argtypes
.Add(wxArgumentType(idx
->second
,
1254 arg
->GetAttribute("default"),
1255 arg
->GetAttribute("name")));
1258 arg
= arg
->GetNext();
1261 m
.SetReturnType(ret
);
1263 m
.SetArgumentTypes(argtypes
);
1264 m
.SetConst(p
->GetAttribute("const") == "1");
1265 m
.SetStatic(p
->GetAttribute("static") == "1");
1267 // NOTE: gccxml is smart enough to mark as virtual those functions
1268 // which are declared virtual in base classes but don't have
1269 // the "virtual" keyword explicitely indicated in the derived
1270 // classes... so we don't need any further logic for virtuals
1272 m
.SetVirtual(p
->GetAttribute("virtual") == "1");
1273 m
.SetPureVirtual(p
->GetAttribute("pure_virtual") == "1");
1274 m
.SetDeprecated(p
->GetAttribute("attributes") == "deprecated");
1276 // decode access specifier
1277 if (p
->GetAttribute("access") == "public")
1278 m
.SetAccessSpecifier(wxMAS_PUBLIC
);
1279 else if (p
->GetAttribute("access") == "protected")
1280 m
.SetAccessSpecifier(wxMAS_PROTECTED
);
1281 else if (p
->GetAttribute("access") == "private")
1282 m
.SetAccessSpecifier(wxMAS_PRIVATE
);
1285 wxLogError("The prototype '%s' is not valid!", m
.GetAsString());
1294 // ----------------------------------------------------------------------------
1295 // wxXmlDoxygenInterface global helpers
1296 // ----------------------------------------------------------------------------
1298 static wxString
GetTextFromChildren(const wxXmlNode
*n
)
1302 // consider the tree
1304 // <a><b>this</b> is a <b>string</b></a>
1313 // unlike wxXmlNode::GetNodeContent() which would return " is a "
1314 // this function returns "this is a string"
1316 wxXmlNode
*ref
= n
->GetChildren();
1318 if (ref
->GetType() == wxXML_ELEMENT_NODE
)
1319 text
+= ref
->GetNodeContent();
1320 else if (ref
->GetType() == wxXML_TEXT_NODE
)
1321 text
+= ref
->GetContent();
1323 wxLogWarning("Unexpected node type while getting text from '%s' node", n
->GetName());
1325 ref
= ref
->GetNext();
1331 static bool HasTextNodeContaining(const wxXmlNode
*parent
, const wxString
& name
)
1336 wxXmlNode
*p
= parent
->GetChildren();
1339 switch (p
->GetType())
1341 case wxXML_TEXT_NODE
:
1342 if (p
->GetContent() == name
)
1346 case wxXML_ELEMENT_NODE
:
1347 // recurse into this node...
1348 if (HasTextNodeContaining(p
, name
))
1363 static const wxXmlNode
* FindNodeNamed(const wxXmlNode
* parent
, const wxString
& name
)
1368 const wxXmlNode
*p
= parent
->GetChildren();
1371 if (p
->GetName() == name
)
1374 // search recursively in the children of this node
1375 const wxXmlNode
*ret
= FindNodeNamed(p
, name
);
1385 int GetAvailabilityFor(const wxXmlNode
*node
)
1387 // identify <onlyfor> custom XML tags
1388 const wxXmlNode
* onlyfor
= FindNodeNamed(node
, "onlyfor");
1390 return wxPORT_UNKNOWN
;
1392 wxArrayString ports
= wxSplit(onlyfor
->GetNodeContent(), ',');
1393 int nAvail
= wxPORT_UNKNOWN
;
1394 for (unsigned int i
=0; i
< ports
.GetCount(); i
++)
1396 if (!ports
[i
].StartsWith("wx")) {
1397 wxLogError("unexpected port ID '%s'", ports
[i
]);
1401 nAvail
|= wxPlatformInfo::GetPortId(ports
[i
].Mid(2));
1408 // ----------------------------------------------------------------------------
1409 // wxXmlDoxygenInterface
1410 // ----------------------------------------------------------------------------
1412 bool wxXmlDoxygenInterface::Parse(const wxString
& filename
)
1414 wxXmlDocument index
;
1415 wxXmlNode
*compound
;
1417 wxLogMessage("Parsing %s...", filename
);
1419 if (!index
.Load(filename
)) {
1420 wxLogError("can't load %s", filename
);
1424 // start processing the index:
1425 if (index
.GetRoot()->GetName() != "doxygenindex") {
1426 wxLogError("invalid root node for %s", filename
);
1431 NB: we may need in future to do a version-check here if the
1432 format of the XML generated by doxygen changes.
1433 For now (doxygen version 1.5.5), this check is not required
1434 since AFAIK the XML format never changed since it was introduced.
1437 m_classes
.Alloc(ESTIMATED_NUM_CLASSES
);
1439 // process files referenced by this index file
1440 compound
= index
.GetRoot()->GetChildren();
1443 if (compound
->GetName() == "compound" &&
1444 compound
->GetAttribute("kind") == "class")
1446 wxString refid
= compound
->GetAttribute("refid");
1448 wxFileName
fn(filename
);
1449 if (!ParseCompoundDefinition(fn
.GetPath(wxPATH_GET_SEPARATOR
) + refid
+ ".xml"))
1453 compound
= compound
->GetNext();
1457 if (!CheckConsistency())
1458 return false; // the check failed
1463 bool wxXmlDoxygenInterface::ParseCompoundDefinition(const wxString
& filename
)
1465 wxClassMemberIdHashMap parents
;
1471 wxLogMessage("Parsing %s...", filename
);
1473 if (!doc
.Load(filename
)) {
1474 wxLogError("can't load %s", filename
);
1478 // start processing this compound definition XML
1479 if (doc
.GetRoot()->GetName() != "doxygen") {
1480 wxLogError("invalid root node for %s", filename
);
1484 // build a list of wx classes
1485 child
= doc
.GetRoot()->GetChildren();
1488 if (child
->GetName() == "compounddef" &&
1489 child
->GetAttribute("kind") == "class")
1493 wxString absoluteFile
, header
;
1495 wxXmlNode
*subchild
= child
->GetChildren();
1498 // NOTE: when documenting functions using the //@{ and //@}
1499 // tags to create function groups, doxygen puts the
1500 // contained methods into a "user-defined" section
1501 // so we _must_ use the "prot" attribute to distinguish
1502 // public/protected methods from private ones and cannot
1503 // rely on the kind="public" attribute of <sectiondef>
1504 if (subchild
->GetName() == "sectiondef")
1506 wxXmlNode
*membernode
= subchild
->GetChildren();
1509 const wxString
& accessSpec
= membernode
->GetAttribute("prot");
1511 // parse only public&protected functions:
1512 if (membernode
->GetName() == "memberdef" &&
1513 membernode
->GetAttribute("kind") == "function" &&
1514 (accessSpec
== "public" || accessSpec
== "protected"))
1518 if (!ParseMethod(membernode
, m
, header
)) {
1519 wxLogError("The method '%s' could not be added to class '%s'",
1520 m
.GetName(), klass
.GetName());
1524 if (accessSpec
== "public")
1525 m
.SetAccessSpecifier(wxMAS_PUBLIC
);
1526 else if (accessSpec
== "protected")
1527 m
.SetAccessSpecifier(wxMAS_PROTECTED
);
1528 else if (accessSpec
== "private")
1529 m
.SetAccessSpecifier(wxMAS_PRIVATE
);
1531 if (absoluteFile
.IsEmpty())
1532 absoluteFile
= header
;
1533 else if (header
!= absoluteFile
)
1535 wxLogError("The method '%s' is documented in a different "
1536 "file from others (which belong to '%s') ?",
1537 header
, absoluteFile
);
1544 membernode
= membernode
->GetNext();
1547 // all methods of this class were taken from the header "absoluteFile":
1548 klass
.SetHeader(absoluteFile
);
1550 else if (subchild
->GetName() == "compoundname")
1552 klass
.SetName(subchild
->GetNodeContent());
1554 /*else if (subchild->GetName() == "includes")
1556 // NOTE: we'll get the header from the <location> tags
1557 // scattered inside <memberdef> tags instead of
1558 // this <includes> tag since it does not contain
1559 // the absolute path of the header
1561 klass.SetHeader(subchild->GetNodeContent());
1563 else if (subchild
->GetName() == "detaileddescription")
1565 // identify <onlyfor> custom XML tags
1566 klass
.SetAvailability(GetAvailabilityFor(subchild
));
1568 else if (subchild
->GetName() == "basecompoundref")
1570 // add the name of this parent to the list of klass' parents
1571 klass
.AddParent(subchild
->GetNodeContent());
1574 subchild
= subchild
->GetNext();
1579 m_classes
.Add(klass
);
1581 wxLogWarning("discarding class '%s' with %d methods...",
1582 klass
.GetName(), klass
.GetMethodCount());
1585 child
= child
->GetNext();
1587 // give feedback to the user about the progress...
1588 if ((++nodes%PROGRESS_RATE
)==0) ShowProgress();
1594 bool wxXmlDoxygenInterface::ParseMethod(const wxXmlNode
* p
, wxMethod
& m
, wxString
& header
)
1596 wxArgumentTypeArray args
;
1599 wxXmlNode
*child
= p
->GetChildren();
1602 if (child
->GetName() == "name")
1603 m
.SetName(child
->GetNodeContent());
1604 else if (child
->GetName() == "type")
1605 m
.SetReturnType(wxType(GetTextFromChildren(child
)));
1606 else if (child
->GetName() == "param")
1608 wxString typestr
, namestr
, defstr
, arrstr
;
1609 wxXmlNode
*n
= child
->GetChildren();
1612 if (n
->GetName() == "type")
1613 // if the <type> node has children, they should be all TEXT and <ref> nodes
1614 // and we need to take the text they contain, in the order they appear
1615 typestr
= GetTextFromChildren(n
);
1616 else if (n
->GetName() == "declname")
1617 namestr
= GetTextFromChildren(n
);
1618 else if (n
->GetName() == "defval")
1619 defstr
= GetTextFromChildren(n
).Strip(wxString::both
);
1620 else if (n
->GetName() == "array")
1621 arrstr
= GetTextFromChildren(n
);
1626 if (typestr
.IsEmpty()) {
1627 wxLogError("cannot find type node for a param in method '%s'", m
.GetName());
1631 wxArgumentType
newarg(typestr
+ arrstr
, defstr
, namestr
);
1633 // can we use preprocessor output to transform the default value
1634 // into the same form which gets processed by wxXmlGccInterface?
1635 wxStringHashMap::const_iterator it
= m_preproc
.find(defstr
);
1636 if (it
!= m_preproc
.end())
1637 newarg
.SetDefaultValue(defstr
, it
->second
);
1641 else if (child
->GetName() == "location")
1644 if (child
->GetAttribute("line").ToLong(&line
))
1645 m
.SetLocation((int)line
);
1646 header
= child
->GetAttribute("file");
1648 else if (child
->GetName() == "detaileddescription")
1650 // when a method has a @deprecated tag inside its description,
1651 // Doxygen outputs somewhere nested inside <detaileddescription>
1652 // a <xreftitle>Deprecated</xreftitle> tag.
1653 m
.SetDeprecated(HasTextNodeContaining(child
, "Deprecated"));
1655 // identify <onlyfor> custom XML tags
1656 m
.SetAvailability(GetAvailabilityFor(child
));
1659 child
= child
->GetNext();
1662 m
.SetArgumentTypes(args
);
1663 m
.SetConst(p
->GetAttribute("const")=="yes");
1664 m
.SetStatic(p
->GetAttribute("static")=="yes");
1666 // NOTE: Doxygen is smart enough to mark as virtual those functions
1667 // which are declared virtual in base classes but don't have
1668 // the "virtual" keyword explicitely indicated in the derived
1669 // classes... so we don't need any further logic for virtuals
1671 m
.SetVirtual(p
->GetAttribute("virt")=="virtual");
1672 m
.SetPureVirtual(p
->GetAttribute("virt")=="pure-virtual");
1675 wxLogError("The prototype '%s' is not valid!", m
.GetAsString());