/****************************************************************************** * * * * Copyright (C) 2009 by Tobias Hunger * * Permission to use, copy, modify, and distribute this software and its * documentation under the terms of the GNU General Public License is hereby * granted. No representations are made about the suitability of this software * for any purpose. It is provided "as is" without express or implied warranty. * See the GNU General Public License for more details. * * Documents produced by Doxygen are derivative works derived from the * input used in their production; they are not affected by this license. * */ #include "dbusxmlscanner.h" #include "commentscan.h" #include "entry.h" #include #include #include #include "message.h" #include "util.h" // ----------------------------------------------------------------------- // Convenience defines: // ----------------------------------------------------------------------- #define CONDITION(cond, msg) \ do {\ if (cond)\ {\ if (m_errorString.isEmpty()) { m_errorString = msg; }\ return false;\ }\ }\ while (0) #define DOC_ERROR(msg) \ warn_doc_error(m_fileName.utf8().data(), lineNumber(), msg.utf8().data()) #define COND_DOC_ERROR(cond, msg) \ do {\ if (cond)\ {\ DOC_ERROR(msg);\ return true;\ }\ }\ while (0) #define DBUS(name) isDBusElement(namespaceURI, localName, qName, name) #define EXTENSION(name) isExtensionElement(namespaceURI, localName, qName, name) // ----------------------------------------------------------------------- // DBusXMLHandler class // ----------------------------------------------------------------------- const QString EXTENSION_URI("http://psiamp.org/dtd/doxygen_dbusxml.dtd"); class DBusXMLHandler : public QXmlDefaultHandler { public: DBusXMLHandler(ParserInterface * parser, QXmlSimpleReader * reader, const char * file_name, Entry * root) : m_parser(parser), m_locator(reader), m_currentEntry(0), m_currentInterface(0), m_currentMethod(0), m_currentArgument(0), m_currentProperty(0), m_currentEnum(0), m_fileName(file_name), m_currentComment(0) { setDocumentLocator(&m_locator); m_scopeCount = 0; // Set up stack cleanup: m_structStack.setAutoDelete(TRUE); m_elementStack.setAutoDelete(TRUE); m_scopeStack.setAutoDelete(TRUE); openScopes(root); } ~DBusXMLHandler() { closeScopes(); } QString errorString() { return m_errorString; } bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &attributes) { // add to elements stack: m_elementStack.append(new ElementData(qName)); // First we need a node. if (DBUS("node")) { CONDITION(!m_currentNode.isEmpty(), "Node inside a node."); const int idx(indexOf(attributes, "name")); COND_DOC_ERROR(idx < 0, QString("Anonymous node found.")); m_currentNode = attributes.value(idx); // A node is actually of little interest, so do nothing here. return true; } // Then we need an interface. if (DBUS("interface")) { // We need a nodeName for interfaces: CONDITION(m_currentNode.isEmpty(), "Interface without a node."); CONDITION(m_currentInterface, "Interface within another interface."); const int idx(indexOf(attributes, "name")); COND_DOC_ERROR(idx < 0, QString("Interface without a name found.")); // A interface is roughly equivalent to a class: m_currentInterface = createEntry(); m_currentInterface->section = Entry::CLASS_SEC; m_currentInterface->spec |= Entry::Interface; m_currentInterface->type = "Interface"; m_currentInterface->name = substitute(attributes.value(idx), ".", "::"); openScopes(m_currentInterface); return true; } if (DBUS("method") || DBUS("signal")) { // We need a interfaceName for methods and signals: CONDITION(!m_currentInterface, "Method or signal found outside a interface."); CONDITION(m_currentMethod, "Method or signal found inside another method or signal."); CONDITION(m_currentProperty, "Methor or signal found inside a property."); CONDITION(!m_structStack.isEmpty(), "Method or signal found inside a struct."); CONDITION(m_currentEnum, "Methor or signal found inside a enum."); const int idx(indexOf(attributes, "name")); COND_DOC_ERROR(idx < 0, QString("Method or signal without a name found.")); m_currentMethod = createEntry(); m_currentMethod->section = Entry::FUNCTION_SEC; m_currentMethod->name = attributes.value(idx); m_currentMethod->mtype = Method; m_currentMethod->type = "void"; if (DBUS("signal")) { m_currentMethod->mtype = Signal; } } if (DBUS("arg")) { // We need a method for arguments: CONDITION(!m_currentMethod, "Argument found outside a method or signal."); CONDITION(m_currentArgument, "Argument found inside another argument."); const int name_idx(indexOf(attributes, "name")); COND_DOC_ERROR(name_idx < 0, QString("Argument without a name found.")); COND_DOC_ERROR(!hasType(attributes), QString("Argument without a type found.")); const int direction_idx(indexOf(attributes, "direction")); if ((m_currentMethod->mtype == Signal && direction_idx >= 0 && attributes.value(direction_idx) != "in") || (m_currentMethod->mtype == Method && direction_idx >= 0 && attributes.value(direction_idx) != "in" && attributes.value(direction_idx) != "out")) { m_errorString = "Invalid direction found."; return false; } m_currentArgument = new Argument; m_currentArgument->type = getType(attributes); m_currentArgument->name = attributes.value(name_idx); if (direction_idx >= 0) { m_currentArgument->attrib = attributes.value(direction_idx); } else { if (m_currentMethod->mtype == Signal) { m_currentArgument->attrib = "in"; } else { m_currentArgument->attrib = "out"; } } } if (DBUS("property")) { CONDITION(m_currentMethod, "Property found inside a method or signal."); CONDITION(!m_currentInterface, "Property found outside an interface."); CONDITION(m_currentProperty, "Property found inside another property."); CONDITION(!m_structStack.isEmpty(), "Property found inside a struct."); CONDITION(m_currentEnum, "Property found inside a enum."); const int name_idx(indexOf(attributes, "name")); COND_DOC_ERROR(name_idx < 0, QString("Anonymous property found.")); COND_DOC_ERROR(!hasType(attributes), QString("Property without a type found.")); const int access_idx(indexOf(attributes, "access")); COND_DOC_ERROR(access_idx < 0, QString("Property without a access attribute found.")); COND_DOC_ERROR(attributes.value(access_idx) != "read" && attributes.value(access_idx) != "write" && attributes.value(access_idx) != "readwrite", QString("Property with invalid access attribute \"%1\" found."). arg(attributes.value(access_idx))); m_currentProperty = createEntry(); m_currentProperty->section = Entry::FUNCTION_SEC; if (attributes.value(access_idx) == "read" || attributes.value(access_idx) == "readwrite") { m_currentProperty->spec |= Entry::Readable; } if (attributes.value(access_idx) == "write" || attributes.value(access_idx) == "readwrite") { m_currentProperty->spec |= Entry::Writable; } m_currentProperty->name = attributes.value(name_idx); m_currentProperty->mtype = Property; m_currentProperty->type = getType(attributes); } if (EXTENSION("namespace")) { CONDITION(m_currentNode.isEmpty(), "Namespace found outside a node."); CONDITION(m_currentInterface, "Namespace found inside an interface."); const int idx(indexOf(attributes, "name")); COND_DOC_ERROR(idx < 0, QString("Anonymous namespace found.")); m_namespaceStack.append(openNamespace(attributes.value(idx))); openScopes(m_namespaceStack.last()); } if (EXTENSION("struct")) { CONDITION(m_currentMethod, "Struct found inside a method or signal."); CONDITION(m_currentProperty, "Struct found inside a property."); CONDITION(m_currentEnum, "Struct found inside an enum."); const int idx(indexOf(attributes, "name")); COND_DOC_ERROR(idx < 0, QString("Anonymous struct found.")); Entry * current_struct = createEntry(); current_struct->section = Entry::CLASS_SEC; current_struct->spec = Entry::Struct; current_struct->name = attributes.value(idx); openScopes(current_struct); current_struct->type = current_struct->name + " struct"; m_structStack.append(new StructData(current_struct)); } if (EXTENSION("member")) { CONDITION(m_structStack.isEmpty(), "Member found outside of struct."); const int name_idx(indexOf(attributes, "name")); COND_DOC_ERROR(name_idx < 0, QString("Anonymous member found.")); COND_DOC_ERROR(!hasType(attributes), QString("Member without a type found.")); createEntry(); m_currentEntry->section = Entry::VARIABLE_SEC; m_currentEntry->name = attributes.value(name_idx); m_currentEntry->type = getType(attributes); QString type(getDBusType(m_currentEntry->type)); m_structStack.last()->type.append(type); } if (EXTENSION("enum") || EXTENSION("flagset")) { CONDITION(m_currentMethod, "Enum found inside a method or signal."); CONDITION(m_currentProperty, "Enum found inside a property."); const int name_idx(indexOf(attributes, "name")); COND_DOC_ERROR(name_idx < 0, QString("Anonymous enum found.")); const int type_idx(indexOf(attributes, "type")); QString type = "u"; if (type_idx >= 0) { type = attributes.value(type_idx); } if (type != "y" && type != "q" && type != "u" && type != "t") { DOC_ERROR(QString("Invalid enum type \"%1\" found.").arg(type)); } m_currentEnum = createEntry(); m_currentEnum->section = Entry::ENUM_SEC; m_currentEnum->name = attributes.value(name_idx); openScopes(m_currentEnum); m_currentEnum->type = m_currentEntry->name + " enum"; addNamedType(type); } if (EXTENSION("value")) { CONDITION(!m_currentEnum, "Value found outside an enum."); const int name_idx(indexOf(attributes, "name")); COND_DOC_ERROR(name_idx < 0, QString("Anonymous value found.")); const int value_idx(indexOf(attributes, "value")); createEntry(); m_currentEntry->section = Entry::VARIABLE_SEC; m_currentEntry->name = attributes.value(name_idx); m_currentEntry->type = m_currentEnum->name; // "@"; // enum marker! if (value_idx >= 0) { m_currentEntry->initializer = attributes.value(value_idx); } } return true; } bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName) { // Clean up elements stack: // Since we made sure to get the elements in the proper order when // adding we do not need to do so again here. COND_DOC_ERROR(m_elementStack.last()->element != qName, QString("Malformed XML: Unexpected closing element found."). arg(m_elementStack.last()->element)); m_elementStack.removeLast(); // Interface: if (DBUS("interface")) { CONDITION(m_currentInterface, "end of interface found without start."); m_currentInterface->endBodyLine = lineNumber(); closeScopes(); m_currentInterface = 0; } if (DBUS("method") || DBUS("signal")) { CONDITION(m_currentMethod, "end of method found without start."); CONDITION(m_currentInterface, "end of method found outside interface."); m_currentMethod->endBodyLine = lineNumber(); m_currentInterface->addSubEntry(m_currentMethod); m_currentMethod = 0; } if (DBUS("property")) { CONDITION(m_currentMethod, "end of property found without start."); CONDITION(m_currentInterface, "end of property found outside interface."); m_currentProperty->endBodyLine = lineNumber(); m_currentInterface->addSubEntry(m_currentProperty); m_currentProperty = 0; } if (DBUS("arg")) { CONDITION(m_currentMethod, "end of arg found outside method."); m_currentMethod->argList->append(m_currentArgument); m_currentArgument = 0; } if (EXTENSION("namespace")) { Entry * current = m_namespaceStack.last(); CONDITION(current, "end of namespace without start."); m_namespaceStack.removeLast(); current->endBodyLine = lineNumber(); closeScopes(); } if (EXTENSION("struct")) { StructData * data = m_structStack.last(); CONDITION(data, "end of struct without start."); data->entry->endBodyLine = lineNumber(); QString current_type; current_type.append(QString("(")); current_type.append(data->type); current_type.append(QString(")")); addNamedType(current_type); closeScopes(); m_structStack.removeLast(); } if (EXTENSION("member")) { StructData * data = m_structStack.last(); CONDITION(data, "end of struct without start"); data->entry->addSubEntry(m_currentEntry); } if (EXTENSION("enum") || EXTENSION("flagset")) { CONDITION(m_currentEnum, "end of enum without start"); m_currentEnum->endBodyLine = lineNumber(); closeScopes(); m_currentEnum = 0; } if (EXTENSION("value")) { CONDITION(m_currentEntry, "end of value without start"); m_currentEntry->endBodyLine = lineNumber(); m_currentEnum->addSubEntry(m_currentEntry); } return true; } bool characters(const QString & /*chars*/) { return true; } bool comment(const QString & comment_) { if (m_currentComment) { handleComment(); } m_currentComment = new CommentData(m_fileName, lineNumber(), comment_); if (!m_currentComment->shouldIgnore) { delete m_currentComment; m_currentComment = 0; return true; } if (m_currentComment->associateWithPrevious) { handleComment(); } return true; } void handleComment() { if (m_currentComment == 0 || m_currentEntry == 0) { return; } QCString text(m_currentComment->text); m_currentEntry->docFile = m_currentComment->fileName; m_currentEntry->docLine = m_currentComment->line; int position(0); bool needs_entry(false); bool brief(false); Protection prot(Public); int lineNr = lineNumber(); while (parseCommentBlock(m_parser, m_currentEntry, text, m_fileName.utf8().data(), lineNr, brief, m_currentComment->isJavaStyle, false, prot, position, needs_entry)) { if (needs_entry) { createEntry(); } } if (needs_entry) { createEntry(); } delete m_currentComment; m_currentComment = 0; } QXmlLocator * locator() { return &m_locator; } int lineNumber() { return m_locator.lineNumber(); } void setSection() { Entry * current = createEntry(); current->reset(); current->name = m_fileName.utf8(); current->section = Entry::SOURCE_SEC; // Open/Close the scope to do the bookkeeping: openScopes(current); closeScopes(); } private: bool isDBusElement(const QString & namespaceURI, const QString & localName, const QString & qName, const QString & element) { return (namespaceURI.isEmpty() && localName == element && qName == element) || (namespaceURI.isEmpty() && localName.isEmpty() && qName == element); } bool isExtensionElement(const QString & namespaceURI, const QString & localName, const QString & qName, const QString & element) { // isNull happens in startelement if no URI is used. if (namespaceURI.isNull()) { return false; } // We are in a endElement: URI is always empty there:-( if (namespaceURI.isEmpty()) { return qName == m_scopeStack.last()->extensionPrefix + element; } // startElemennt: We need to save the qName prefix // since endElement will forget about the namespaceURi:-( if (namespaceURI == EXTENSION_URI) { int pos = qName.find(':'); m_scopeStack.last()->extensionPrefix = qName.left(pos + 1); } return namespaceURI == EXTENSION_URI && localName == element; } bool hasType(const QXmlAttributes & attributes) { const int type_idx(indexOf(attributes, "type")); const int named_type_idx(indexOf(attributes, "named-type")); return named_type_idx >= 0 || type_idx >= 0; } QString getType(const QXmlAttributes & attributes) { const int type_idx(indexOf(attributes, "type")); const int named_type_idx(indexOf(attributes, "named-type")); QString type; if (named_type_idx >= 0) { type = attributes.value(named_type_idx); if (!type.startsWith("::")) { type = getCurrentScope(attributes.value(named_type_idx)); } else { type = type.mid(2); } if (m_namedTypeMap.contains(type)) { if (type_idx >= 0) { const QString dbus_type(attributes.value(type_idx)); if (dbus_type != m_namedTypeMap[type]) { DOC_ERROR(QString("Type \"%1\" does not match up with " "previous definition of named type \"%2\" (which was \"%3\"."). arg(dbus_type). arg(type). arg(m_namedTypeMap[type])); } } return type; } DOC_ERROR(QString("Undefined named type \"%1\" used.").arg(type)); } if (type_idx >= 0) { type = attributes.value(type_idx); QRegExp reg_exp(QString("(a?[ybnqiuxdtsogv]|a[{]sv[}])")); if (reg_exp.match(type)) { return type; } DOC_ERROR(QString("Unnamed complex D-Bus type \"%1\" found.").arg(type)); } return QString(); } QString getDBusType(const QString & type) { QString scoped_type = type; if (!scoped_type.contains("::")) { scoped_type = getCurrentScope(type); } if (m_namedTypeMap.contains(scoped_type)) { return m_namedTypeMap[scoped_type]; } else { return type; } } void addNamedType(const QString type) { QString scoped_name(getCurrentScope()); if (m_namedTypeMap.contains(scoped_name)) { DOC_ERROR(QString("Named type \"%1\" is already defined.").arg(scoped_name)); return; } m_namedTypeMap.insert(scoped_name, type); } QString getCurrentScope(const QString & type = QString()) { QString scoped_name; if (!m_scopeStack.isEmpty()) { scoped_name = m_scopeStack.last()->scope->name; scoped_name.append("::"); } if (!type.isEmpty()) { scoped_name.append(type); } else { scoped_name = scoped_name.left(scoped_name.length() - 2); } return scoped_name; } int indexOf(const QXmlAttributes & attributes, const QString & name, const QString & type = "CDATA", const bool mandatory = true) { const int idx(attributes.index(name)); if (idx < 0 || idx > attributes.length()) { return -1; } if (attributes.type(idx) != type) { return -1; } if (mandatory && attributes.value(idx).isEmpty()) { return -1; } return idx; } Entry * createEntry() { Entry * entry = new Entry(); entry->protection = Public ; entry->virt = Normal; entry->stat = false; entry->objc = false; entry->spec = 0; entry->fileName = m_fileName; entry->startLine = lineNumber(); entry->bodyLine = lineNumber(); entry->callGraph = false; entry->callerGraph = false; initGroupInfo(entry); m_currentEntry = entry; handleComment(); return entry; } void openScopes(Entry * object) { int cur_scope_separator_pos = 0; int last_scope_separator_pos = 0; while (0 <= (cur_scope_separator_pos = object->name.find("::", last_scope_separator_pos))) { QString scope = object->name.mid(last_scope_separator_pos, cur_scope_separator_pos - last_scope_separator_pos); last_scope_separator_pos = cur_scope_separator_pos + 2; Entry * current_namespace = openNamespace(scope); if (!m_scopeStack.isEmpty()) { m_scopeStack.last()->scope->addSubEntry(current_namespace); } m_scopeStack.append(new ScopeData(current_namespace, m_scopeCount)); } QString scoped_name(getCurrentScope()); if (!scoped_name.isEmpty()) { scoped_name.append("::"); } scoped_name.append(object->name.mid(last_scope_separator_pos)); object->name = scoped_name; if (!m_scopeStack.isEmpty()) { m_scopeStack.last()->scope->addSubEntry(object); } m_scopeStack.append(new ScopeData(object, m_scopeCount)); ++m_scopeCount; } Entry * openNamespace(const QString & name) { Entry * current_namespace = createEntry(); QString scoped_name(getCurrentScope()); if (!scoped_name.isEmpty()) { scoped_name.append("::"); } scoped_name.append(name); current_namespace->name = scoped_name; current_namespace->section = Entry::NAMESPACE_SEC; current_namespace->type = "namespace" ; return current_namespace; } void closeScopes() { const int current_scope_count(m_scopeStack.last()->count); // Do not close the root scope. if (current_scope_count == 0) { return; } while (current_scope_count == m_scopeStack.last()->count) { m_scopeStack.removeLast(); } } ParserInterface * m_parser; QXmlLocator m_locator; QString m_currentNode; // Nodes can not be nested, no entry necessary. struct ElementData { ElementData(const QString & e) : element(e) { } ~ElementData() { } QString element; //*< The element name QString extensionPrefix; //*< The prefix used for our extension. QString text; //*< The actual xml code. }; QList m_elementStack; Entry * m_currentEntry; // The currently open entry. Entry * m_currentInterface; // Interfaces can not be nested. Entry * m_currentMethod; // Methods can not be nested. Argument * m_currentArgument; // Arguments can not be nested. Entry * m_currentProperty; // Properties can not be nested. Entry * m_currentEnum; // Enums can not be nested. QList m_namespaceStack; struct StructData { StructData(Entry * e) : entry(e) { } ~StructData() { } QString type; Entry * entry; }; QList m_structStack; // Structs can be nested. struct ScopeData { ScopeData(Entry * s, int c) : scope(s), count(c) { } ~ScopeData() { } Entry * scope; QString extensionPrefix; int count; }; QList m_scopeStack; // Scopes are nested. QString m_fileName; struct CommentData { CommentData(const QString & f, const int l, const QString & t) : isJavaStyle(false), isQtStyle(false), line(l), fileName(f) { isJavaStyle = t.startsWith(QChar('*')); isQtStyle = t.startsWith(QChar('!')); shouldIgnore = (!isJavaStyle && !isQtStyle); associateWithPrevious = (t.at(1) == QChar('<')); if (associateWithPrevious) { text = t.mid(2); } else { text = t.mid(1); } } ~CommentData() { } QString text; bool isJavaStyle; bool isQtStyle; bool shouldIgnore; bool associateWithPrevious; int line; QString fileName; }; CommentData * m_currentComment; int m_scopeCount; //*< unique scope id. QString m_errorString; QMap m_namedTypeMap; }; // ----------------------------------------------------------------------- // DBusXMLScanner // ----------------------------------------------------------------------- DBusXMLScanner::DBusXMLScanner() { } DBusXMLScanner::~DBusXMLScanner() { } void DBusXMLScanner::parseInput(const char * fileName, const char * /* fileBuf */, Entry * root) { QFile inputFile(fileName); QXmlInputSource inputSource(inputFile); QXmlSimpleReader reader; DBusXMLHandler handler(this, &reader, fileName, root); reader.setContentHandler(&handler); reader.setErrorHandler(&handler); reader.setLexicalHandler(&handler); groupEnterFile(fileName, 1); handler.setSection(); reader.parse(inputSource); if (handler.errorString()) { err("DBus XML Parser: Error at line %d: %s\n", handler.locator()->lineNumber(),handler.errorString().utf8().data()); } groupLeaveFile(fileName, 1); } bool DBusXMLScanner::needsPreprocessing(const QCString & /* extension */) { return (false); } void DBusXMLScanner::parseCode(CodeOutputInterface & /* codeOutIntf */, const char * /* scopeName */, const QCString & /* input */, bool /* isExampleBlock */, const char * /* exampleName */, FileDef * /* fileDef */, int /* startLine */, int /* endLine */, bool /* inlineFragment */, MemberDef * /* memberDef */, bool /*showLineNumbers*/) { } void DBusXMLScanner::resetCodeParserState() { } void DBusXMLScanner::parsePrototype(const char * /* text */) { }