1 /******************************************************************************
5 * Copyright (C) 2009 by Tobias Hunger <tobias@aquazul.com>
7 * Permission to use, copy, modify, and distribute this software and its
8 * documentation under the terms of the GNU General Public License is hereby
9 * granted. No representations are made about the suitability of this software
10 * for any purpose. It is provided "as is" without express or implied warranty.
11 * See the GNU General Public License for more details.
13 * Documents produced by Doxygen are derivative works derived from the
14 * input used in their production; they are not affected by this license.
18 #include "dbusxmlscanner.h"
20 #include "commentscan.h"
29 #include "arguments.h"
31 // -----------------------------------------------------------------------
32 // Convenience defines:
33 // -----------------------------------------------------------------------
35 #define CONDITION(cond, msg) \
39 if (m_errorString.isEmpty()) { m_errorString = msg; }\
45 #define DOC_ERROR(msg) \
46 warn_doc_error(m_fileName.data(), lineNumber(), msg.data())
48 #define COND_DOC_ERROR(cond, msg) \
58 #define DBUS(name) isDBusElement(namespaceURI, localName, qName, name)
59 #define EXTENSION(name) isExtensionElement(namespaceURI, localName, qName, name)
61 // -----------------------------------------------------------------------
62 // DBusXMLHandler class
63 // -----------------------------------------------------------------------
65 const QString EXTENSION_URI("http://psiamp.org/dtd/doxygen_dbusxml.dtd");
67 /** DBus implementation of the generic QXmlDefaultHandler. */
68 class DBusXMLHandler : public QXmlDefaultHandler
71 DBusXMLHandler(ParserInterface * parser,
72 QXmlSimpleReader * reader,
73 const char * file_name,
78 m_currentInterface(0),
83 m_fileName(file_name),
86 setDocumentLocator(&m_locator);
90 // Set up stack cleanup:
91 m_structStack.setAutoDelete(TRUE);
92 m_elementStack.setAutoDelete(TRUE);
93 m_scopeStack.setAutoDelete(TRUE);
101 QString errorString()
102 { return m_errorString; }
104 bool startElement(const QString &namespaceURI,
105 const QString &localName,
106 const QString &qName,
107 const QXmlAttributes &attributes)
109 // add to elements stack:
110 m_elementStack.append(new ElementData(qName.utf8()));
112 // First we need a node.
115 CONDITION(!m_currentNode.isEmpty(), "Node inside a node.");
117 const int idx(indexOf(attributes, "name"));
118 COND_DOC_ERROR(idx < 0, QCString("Anonymous node found."));
120 m_currentNode = attributes.value(idx).utf8();
121 // A node is actually of little interest, so do nothing here.
125 // Then we need an interface.
126 if (DBUS("interface"))
128 // We need a nodeName for interfaces:
129 CONDITION(m_currentNode.isEmpty(), "Interface without a node.");
130 CONDITION(m_currentInterface, "Interface within another interface.");
132 const int idx(indexOf(attributes, "name"));
133 COND_DOC_ERROR(idx < 0, QString("Interface without a name found."));
135 // A interface is roughly equivalent to a class:
136 m_currentInterface = createEntry();
138 m_currentInterface->section = Entry::CLASS_SEC;
139 m_currentInterface->spec |= Entry::Interface;
140 m_currentInterface->type = "Interface";
141 m_currentInterface->name = substitute(attributes.value(idx).utf8(), ".", "::");
143 openScopes(m_currentInterface);
148 if (DBUS("method") || DBUS("signal"))
150 // We need a interfaceName for methods and signals:
151 CONDITION(!m_currentInterface, "Method or signal found outside a interface.");
152 CONDITION(m_currentMethod, "Method or signal found inside another method or signal.");
153 CONDITION(m_currentProperty, "Methor or signal found inside a property.");
154 CONDITION(!m_structStack.isEmpty(), "Method or signal found inside a struct.");
155 CONDITION(m_currentEnum, "Methor or signal found inside a enum.");
157 const int idx(indexOf(attributes, "name"));
158 COND_DOC_ERROR(idx < 0, QString("Method or signal without a name found."));
160 m_currentMethod = createEntry();
162 m_currentMethod->section = Entry::FUNCTION_SEC;
163 m_currentMethod->name = attributes.value(idx).utf8();
164 m_currentMethod->mtype = Method;
165 m_currentMethod->type = "void";
168 { m_currentMethod->mtype = Signal; }
173 // We need a method for arguments:
174 CONDITION(!m_currentMethod, "Argument found outside a method or signal.");
175 CONDITION(m_currentArgument, "Argument found inside another argument.");
177 const int name_idx(indexOf(attributes, "name"));
178 COND_DOC_ERROR(name_idx < 0, QString("Argument without a name found."));
179 COND_DOC_ERROR(!hasType(attributes), QString("Argument without a type found."));
181 const int direction_idx(indexOf(attributes, "direction"));
183 if ((m_currentMethod->mtype == Signal &&
184 direction_idx >= 0 &&
185 attributes.value(direction_idx) != "in") ||
186 (m_currentMethod->mtype == Method &&
187 direction_idx >= 0 &&
188 attributes.value(direction_idx) != "in" &&
189 attributes.value(direction_idx) != "out"))
191 m_errorString = "Invalid direction found.";
195 m_currentArgument = new Argument;
196 m_currentArgument->type = getType(attributes).utf8();
197 m_currentArgument->name = attributes.value(name_idx).utf8();
198 if (direction_idx >= 0)
199 { m_currentArgument->attrib = attributes.value(direction_idx).utf8(); }
202 if (m_currentMethod->mtype == Signal)
203 { m_currentArgument->attrib = "in"; }
205 { m_currentArgument->attrib = "out"; }
209 if (DBUS("property"))
211 CONDITION(m_currentMethod, "Property found inside a method or signal.");
212 CONDITION(!m_currentInterface, "Property found outside an interface.");
213 CONDITION(m_currentProperty, "Property found inside another property.");
214 CONDITION(!m_structStack.isEmpty(), "Property found inside a struct.");
215 CONDITION(m_currentEnum, "Property found inside a enum.");
217 const int name_idx(indexOf(attributes, "name"));
218 COND_DOC_ERROR(name_idx < 0, QString("Anonymous property found."));
219 COND_DOC_ERROR(!hasType(attributes), QString("Property without a type found."));
221 const int access_idx(indexOf(attributes, "access"));
222 COND_DOC_ERROR(access_idx < 0, QString("Property without a access attribute found."));
223 COND_DOC_ERROR(attributes.value(access_idx) != "read" &&
224 attributes.value(access_idx) != "write" &&
225 attributes.value(access_idx) != "readwrite",
226 QString("Property with invalid access attribute \"%1\" found.").
227 arg(attributes.value(access_idx)));
229 m_currentProperty = createEntry();
231 m_currentProperty->section = Entry::FUNCTION_SEC;
233 if (attributes.value(access_idx) == "read" ||
234 attributes.value(access_idx) == "readwrite")
235 { m_currentProperty->spec |= Entry::Readable; }
237 if (attributes.value(access_idx) == "write" ||
238 attributes.value(access_idx) == "readwrite")
239 { m_currentProperty->spec |= Entry::Writable; }
241 m_currentProperty->name = attributes.value(name_idx).utf8();
242 m_currentProperty->mtype = Property;
243 m_currentProperty->type = getType(attributes).utf8();
246 if (EXTENSION("namespace"))
248 CONDITION(m_currentNode.isEmpty(), "Namespace found outside a node.");
249 CONDITION(m_currentInterface, "Namespace found inside an interface.");
251 const int idx(indexOf(attributes, "name"));
252 COND_DOC_ERROR(idx < 0, QString("Anonymous namespace found."));
254 m_namespaceStack.append(openNamespace(attributes.value(idx)));
255 openScopes(m_namespaceStack.last());
258 if (EXTENSION("struct"))
260 CONDITION(m_currentMethod, "Struct found inside a method or signal.");
261 CONDITION(m_currentProperty, "Struct found inside a property.");
262 CONDITION(m_currentEnum, "Struct found inside an enum.");
264 const int idx(indexOf(attributes, "name"));
265 COND_DOC_ERROR(idx < 0, QString("Anonymous struct found."));
267 Entry * current_struct = createEntry();
268 current_struct->section = Entry::CLASS_SEC;
269 current_struct->spec = Entry::Struct;
270 current_struct->name = attributes.value(idx).utf8();
272 openScopes(current_struct);
274 current_struct->type = current_struct->name + " struct";
276 m_structStack.append(new StructData(current_struct));
279 if (EXTENSION("member"))
281 CONDITION(m_structStack.isEmpty(), "Member found outside of struct.");
283 const int name_idx(indexOf(attributes, "name"));
284 COND_DOC_ERROR(name_idx < 0, QString("Anonymous member found."));
285 COND_DOC_ERROR(!hasType(attributes), QString("Member without a type found."));
289 m_currentEntry->section = Entry::VARIABLE_SEC;
290 m_currentEntry->name = attributes.value(name_idx).utf8();
291 m_currentEntry->type = getType(attributes).utf8();
293 QString type(getDBusType(m_currentEntry->type));
294 m_structStack.last()->type.append(type.utf8());
297 if (EXTENSION("enum") || EXTENSION("flagset"))
299 CONDITION(m_currentMethod, "Enum found inside a method or signal.");
300 CONDITION(m_currentProperty, "Enum found inside a property.");
302 const int name_idx(indexOf(attributes, "name"));
303 COND_DOC_ERROR(name_idx < 0, QString("Anonymous enum found."));
305 const int type_idx(indexOf(attributes, "type"));
308 { type = attributes.value(type_idx); }
309 if (type != "y" && type != "q" && type != "u" && type != "t")
310 { DOC_ERROR(QString("Invalid enum type \"%1\" found.").arg(type)); }
312 m_currentEnum = createEntry();
313 m_currentEnum->section = Entry::ENUM_SEC;
314 m_currentEnum->name = attributes.value(name_idx).utf8();
316 openScopes(m_currentEnum);
318 m_currentEnum->type = m_currentEntry->name + " enum";
320 addNamedType(type.utf8());
323 if (EXTENSION("value"))
325 CONDITION(!m_currentEnum, "Value found outside an enum.");
327 const int name_idx(indexOf(attributes, "name"));
328 COND_DOC_ERROR(name_idx < 0, QString("Anonymous value found."));
330 const int value_idx(indexOf(attributes, "value"));
334 m_currentEntry->section = Entry::VARIABLE_SEC;
335 m_currentEntry->name = attributes.value(name_idx).utf8();
336 m_currentEntry->type = m_currentEnum->name; // "@"; // enum marker!
338 { m_currentEntry->initializer = attributes.value(value_idx).utf8(); }
344 bool endElement(const QString &namespaceURI,
345 const QString &localName,
346 const QString &qName)
348 // Clean up elements stack:
349 // Since we made sure to get the elements in the proper order when
350 // adding we do not need to do so again here.
351 COND_DOC_ERROR(m_elementStack.last()->element != qName.utf8(),
352 QString("Malformed XML: Unexpected closing element found.").
353 arg(m_elementStack.last()->element).utf8());
354 m_elementStack.removeLast();
357 if (DBUS("interface"))
359 CONDITION(!m_currentInterface, "end of interface found without start.");
360 m_currentInterface->endBodyLine = lineNumber();
362 m_currentInterface = 0;
365 if (DBUS("method") || DBUS("signal"))
367 CONDITION(!m_currentMethod, "end of method found without start.");
368 CONDITION(!m_currentInterface, "end of method found outside interface.");
369 m_currentMethod->endBodyLine = lineNumber();
370 m_currentInterface->addSubEntry(m_currentMethod);
374 if (DBUS("property"))
376 CONDITION(!m_currentProperty, "end of property found without start.");
377 CONDITION(!m_currentInterface, "end of property found outside interface.");
378 m_currentProperty->endBodyLine = lineNumber();
379 m_currentInterface->addSubEntry(m_currentProperty);
380 m_currentProperty = 0;
385 CONDITION(!m_currentMethod, "end of arg found outside method.");
386 m_currentMethod->argList->append(m_currentArgument);
387 m_currentArgument = 0;
390 if (EXTENSION("namespace"))
392 Entry * current = m_namespaceStack.last();
393 CONDITION(!current, "end of namespace without start.");
394 m_namespaceStack.removeLast();
396 current->endBodyLine = lineNumber();
400 if (EXTENSION("struct"))
402 StructData * data = m_structStack.last();
403 CONDITION(!data, "end of struct without start.");
405 data->entry->endBodyLine = lineNumber();
407 QString current_type;
408 current_type.append(QString("("));
409 current_type.append(data->type);
410 current_type.append(QString(")"));
412 addNamedType(current_type.utf8());
416 m_structStack.removeLast();
419 if (EXTENSION("member"))
421 StructData * data = m_structStack.last();
422 CONDITION(!data, "end of member outside struct.");
423 data->entry->addSubEntry(m_currentEntry);
426 if (EXTENSION("enum") || EXTENSION("flagset"))
428 CONDITION(!m_currentEnum, "end of enum without start.");
429 m_currentEnum->endBodyLine = lineNumber();
435 if (EXTENSION("value"))
437 CONDITION(!m_currentEntry, "end of value without start");
438 m_currentEntry->endBodyLine = lineNumber();
440 m_currentEnum->addSubEntry(m_currentEntry);
446 bool characters(const QString & /*chars*/)
449 bool comment(const QString & comment_)
451 if (m_currentComment)
454 m_currentComment = new CommentData(m_fileName, lineNumber(), comment_.utf8());
456 if (m_currentComment->shouldIgnore)
458 delete m_currentComment;
459 m_currentComment = 0;
463 if (m_currentComment->associateWithPrevious)
471 if (m_currentComment == 0 || m_currentEntry == 0)
474 QCString text(m_currentComment->text);
476 m_currentEntry->docFile = m_currentComment->fileName;
477 m_currentEntry->docLine = m_currentComment->line;
480 bool needs_entry(false);
482 Protection prot(Public);
483 int lineNr = lineNumber();
485 while (parseCommentBlock(m_parser,
487 text, m_fileName.data(),
489 brief, m_currentComment->isJavaStyle,
495 if (needs_entry) { createEntry(); }
497 if (needs_entry) { createEntry(); }
499 delete m_currentComment;
500 m_currentComment = 0;
503 QXmlLocator * locator()
504 { return &m_locator; }
507 { return m_locator.lineNumber(); }
511 Entry * current = createEntry();
514 current->name = m_fileName;
515 current->section = Entry::SOURCE_SEC;
517 // Open/Close the scope to do the bookkeeping:
523 bool isDBusElement(const QString & namespaceURI,
524 const QString & localName,
525 const QString & qName,
526 const QString & element)
528 return (namespaceURI.isEmpty() && localName == element && qName == element) ||
529 (namespaceURI.isEmpty() && localName.isEmpty() && qName == element);
532 bool isExtensionElement(const QString & namespaceURI,
533 const QString & localName,
534 const QString & qName,
535 const QString & element)
539 return namespaceURI == EXTENSION_URI && localName == element;
542 bool hasType(const QXmlAttributes & attributes)
544 const int type_idx(indexOf(attributes, "type"));
545 const int named_type_idx(indexOf(attributes, "named-type"));
547 return named_type_idx >= 0 || type_idx >= 0;
550 QString getType(const QXmlAttributes & attributes)
552 const int type_idx(indexOf(attributes, "type"));
553 const int named_type_idx(indexOf(attributes, "named-type"));
557 if (named_type_idx >= 0)
559 type = attributes.value(named_type_idx).utf8();
560 if (type.left(2)!="::")
561 { type = getCurrentScope(attributes.value(named_type_idx).utf8()); }
563 { type = type.mid(2); }
564 if (m_namedTypeMap.contains(type))
568 const QCString dbus_type(attributes.value(type_idx).utf8());
569 if (dbus_type != m_namedTypeMap[type])
571 DOC_ERROR(QString("Type \"%1\" does not match up with "
572 "previous definition of named type \"%2\" (which was \"%3\".").
575 arg(m_namedTypeMap[type]));
581 DOC_ERROR(QString("Undefined named type \"%1\" used.").arg(type));
586 type = attributes.value(type_idx).utf8();
588 QRegExp reg_exp(QCString("(a?[ybnqiuxdtsogv]|a[{]sv[}])"));
589 if (reg_exp.match(type.data()))
592 DOC_ERROR(QString("Unnamed complex D-Bus type \"%1\" found.").arg(type));
598 QString getDBusType(const QCString & type)
600 QCString scoped_type = type;
601 if (!scoped_type.contains("::"))
602 { scoped_type = getCurrentScope(type); }
604 if (m_namedTypeMap.contains(scoped_type))
605 { return m_namedTypeMap[scoped_type]; }
610 void addNamedType(const QCString &type)
612 QCString scoped_name(getCurrentScope());
614 if (m_namedTypeMap.contains(scoped_name))
616 DOC_ERROR(QString("Named type \"%1\" is already defined.").arg(scoped_name));
620 m_namedTypeMap.insert(scoped_name, type);
623 QCString getCurrentScope(const QCString & type = QCString())
625 QCString scoped_name;
626 if (!m_scopeStack.isEmpty())
628 scoped_name = m_scopeStack.last()->scope->name;
629 scoped_name.append("::");
632 { scoped_name.append(type); }
634 { scoped_name = scoped_name.left(scoped_name.length() - 2); }
639 int indexOf(const QXmlAttributes & attributes, const QString & name,
640 const QString & type = "CDATA", const bool mandatory = true)
642 const int idx(attributes.index(name));
643 if (idx < 0 || idx > attributes.length()) { return -1; }
644 if (attributes.type(idx) != type) { return -1; }
645 if (mandatory && attributes.value(idx).isEmpty()) { return -1; }
650 Entry * createEntry()
652 Entry * entry = new Entry();
654 entry->protection = Public ;
655 entry->virt = Normal;
657 entry->lang = SrcLangExt_XML;
660 entry->fileName = m_fileName;
661 entry->startLine = lineNumber();
662 entry->bodyLine = lineNumber();
664 entry->callGraph = false;
665 entry->callerGraph = false;
667 initGroupInfo(entry);
669 m_currentEntry = entry;
676 void openScopes(Entry * object)
678 int cur_scope_separator_pos = 0;
679 int last_scope_separator_pos = 0;
680 while (0 <= (cur_scope_separator_pos = object->name.find("::", last_scope_separator_pos)))
682 QString scope = object->name.mid(last_scope_separator_pos,
683 cur_scope_separator_pos - last_scope_separator_pos);
684 last_scope_separator_pos = cur_scope_separator_pos + 2;
686 Entry * current_namespace = openNamespace(scope);
688 if (!m_scopeStack.isEmpty())
689 { m_scopeStack.last()->scope->addSubEntry(current_namespace); }
691 m_scopeStack.append(new ScopeData(current_namespace, m_scopeCount));
694 QCString scoped_name(getCurrentScope());
695 if (!scoped_name.isEmpty())
696 { scoped_name.append("::"); }
697 scoped_name.append(object->name.mid(last_scope_separator_pos));
699 object->name = scoped_name;
701 if (!m_scopeStack.isEmpty())
702 { m_scopeStack.last()->scope->addSubEntry(object); }
703 m_scopeStack.append(new ScopeData(object, m_scopeCount));
708 Entry * openNamespace(const QString & name)
710 Entry * current_namespace = createEntry();
711 QCString scoped_name(getCurrentScope());
712 if (!scoped_name.isEmpty())
713 { scoped_name.append("::"); }
714 scoped_name.append(name.utf8());
715 current_namespace->name = scoped_name;
716 current_namespace->section = Entry::NAMESPACE_SEC;
717 current_namespace->type = "namespace" ;
719 return current_namespace;
724 const int current_scope_count(m_scopeStack.last()->count);
726 // Do not close the root scope.
727 if (current_scope_count == 0)
730 while (current_scope_count == m_scopeStack.last()->count)
731 { m_scopeStack.removeLast(); }
734 ParserInterface * m_parser;
736 QXmlLocator m_locator;
737 QCString m_currentNode; // Nodes can not be nested, no entry necessary.
741 ElementData(const QCString & e) :
746 QCString element; //*< The element name
747 QCString text; //*< The actual xml code.
749 QList<ElementData> m_elementStack;
751 Entry * m_currentEntry; // The currently open entry.
753 Entry * m_currentInterface; // Interfaces can not be nested.
754 Entry * m_currentMethod; // Methods can not be nested.
755 Argument * m_currentArgument; // Arguments can not be nested.
756 Entry * m_currentProperty; // Properties can not be nested.
757 Entry * m_currentEnum; // Enums can not be nested.
758 QList<Entry> m_namespaceStack;
762 StructData(Entry * e) : entry(e) { }
768 QList<StructData> m_structStack; // Structs can be nested.
772 ScopeData(Entry * s, int c) :
781 QList<ScopeData> m_scopeStack; // Scopes are nested.
787 CommentData(const QCString & f, const int l, const QCString & t) :
793 isJavaStyle = t.length()>0 && t.at(0)=='*';
794 isQtStyle = t.length()>0 && t.at(0)=='!';
795 shouldIgnore = (!isJavaStyle && !isQtStyle);
796 associateWithPrevious = (t.length()>1 && t.at(1)=='<');
797 if (associateWithPrevious)
808 bool associateWithPrevious;
812 CommentData * m_currentComment;
814 int m_scopeCount; //*< unique scope id.
816 QString m_errorString;
818 QMap<QCString, QCString> m_namedTypeMap;
821 // -----------------------------------------------------------------------
823 // -----------------------------------------------------------------------
825 DBusXMLScanner::DBusXMLScanner()
828 DBusXMLScanner::~DBusXMLScanner()
831 void DBusXMLScanner::parseInput(const char * fileName,
832 const char * /* fileBuf */,
835 QFile inputFile(fileName);
837 QXmlInputSource inputSource(inputFile);
838 QXmlSimpleReader reader;
840 DBusXMLHandler handler(this, &reader, fileName, root);
841 reader.setContentHandler(&handler);
842 reader.setErrorHandler(&handler);
843 reader.setLexicalHandler(&handler);
845 groupEnterFile(fileName, 1);
846 handler.setSection();
847 reader.parse(inputSource);
849 if (!handler.errorString().isEmpty())
850 { err("DBus XML Parser: Error at line %d: %s\n",
851 handler.locator()->lineNumber(),handler.errorString().utf8().data()); }
853 groupLeaveFile(fileName, 1);
856 bool DBusXMLScanner::needsPreprocessing(const QCString & /* extension */)
859 void DBusXMLScanner::parseCode(CodeOutputInterface & /* codeOutIntf */,
860 const char * /* scopeName */,
861 const QCString & /* input */,
862 bool /* isExampleBlock */,
863 const char * /* exampleName */,
864 FileDef * /* fileDef */,
867 bool /* inlineFragment */,
868 MemberDef * /* memberDef */,
869 bool /*showLineNumbers*/,
870 Definition * /* searchCtx */)
873 void DBusXMLScanner::resetCodeParserState()
876 void DBusXMLScanner::parsePrototype(const char * /* text */)