/* * This file is part of the PySide project. * * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). * * Contact: PySide team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "qtdocgenerator.h" #include #include #include #include #include #include #include #include #include #include #include #include EXPORT_GENERATOR_PLUGIN(new QtDocGenerator) static Indentor INDENT; static bool shouldSkip(const AbstractMetaFunction* func) { bool skipable = func->isConstructor() || func->isModifiedRemoved() || func->declaringClass() != func->ownerClass() || func->isCastOperator() || func->name() == "operator="; // Search a const clone if (!skipable && !func->isConstant()) { const AbstractMetaArgumentList funcArgs = func->arguments(); foreach (AbstractMetaFunction* f, func->ownerClass()->functions()) { if (f != func && f->isConstant() && f->name() == func->name() && f->arguments().count() == funcArgs.count()) { // Compare each argument bool cloneFound = true; const AbstractMetaArgumentList fargs = f->arguments(); for (int i = 0, max = funcArgs.count(); i < max; ++i) { if (funcArgs.at(i)->type()->typeEntry() != fargs.at(i)->type()->typeEntry()) { cloneFound = false; break; } } if (cloneFound) return true; } } } return skipable; } static bool functionSort(const AbstractMetaFunction* func1, const AbstractMetaFunction* func2) { return func1->name() < func2->name(); } static QString createRepeatedChar(int i, char c) { QString out; for (int j = 0; j < i; ++j) out += c; return out; } static QString escape(QString& str) { return str .replace("*", "\\*") .replace("_", "\\_"); } static QString escape(const QStringRef& strref) { QString str = strref.toString(); return escape(str); } QtXmlToSphinx::QtXmlToSphinx(QtDocGenerator* generator, const QString& doc, const QString& context) : m_context(context), m_generator(generator), m_insideBold(false), m_insideItalic(false) { m_handlerMap.insert("heading", &QtXmlToSphinx::handleHeadingTag); m_handlerMap.insert("brief", &QtXmlToSphinx::handleParaTag); m_handlerMap.insert("para", &QtXmlToSphinx::handleParaTag); m_handlerMap.insert("italic", &QtXmlToSphinx::handleItalicTag); m_handlerMap.insert("bold", &QtXmlToSphinx::handleBoldTag); m_handlerMap.insert("see-also", &QtXmlToSphinx::handleSeeAlsoTag); m_handlerMap.insert("snippet", &QtXmlToSphinx::handleSnippetTag); m_handlerMap.insert("dots", &QtXmlToSphinx::handleDotsTag); m_handlerMap.insert("codeline", &QtXmlToSphinx::handleDotsTag); m_handlerMap.insert("table", &QtXmlToSphinx::handleTableTag); m_handlerMap.insert("header", &QtXmlToSphinx::handleRowTag); m_handlerMap.insert("row", &QtXmlToSphinx::handleRowTag); m_handlerMap.insert("item", &QtXmlToSphinx::handleItemTag); m_handlerMap.insert("argument", &QtXmlToSphinx::handleArgumentTag); m_handlerMap.insert("teletype", &QtXmlToSphinx::handleArgumentTag); m_handlerMap.insert("link", &QtXmlToSphinx::handleLinkTag); m_handlerMap.insert("inlineimage", &QtXmlToSphinx::handleImageTag); m_handlerMap.insert("image", &QtXmlToSphinx::handleImageTag); m_handlerMap.insert("list", &QtXmlToSphinx::handleListTag); m_handlerMap.insert("term", &QtXmlToSphinx::handleTermTag); m_handlerMap.insert("raw", &QtXmlToSphinx::handleRawTag); m_handlerMap.insert("underline", &QtXmlToSphinx::handleItalicTag); m_handlerMap.insert("superscript", &QtXmlToSphinx::handleSuperScriptTag); m_handlerMap.insert("code", &QtXmlToSphinx::handleCodeTag); m_handlerMap.insert("badcode", &QtXmlToSphinx::handleCodeTag); m_handlerMap.insert("legalese", &QtXmlToSphinx::handleCodeTag); m_handlerMap.insert("section", &QtXmlToSphinx::handleAnchorTag); m_handlerMap.insert("quotefile", &QtXmlToSphinx::handleQuoteFileTag); // ignored tags m_handlerMap.insert("generatedlist", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("tableofcontents", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("quotefromfile", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("skipto", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("target", &QtXmlToSphinx::handleIgnoredTag); // useless tags m_handlerMap.insert("description", &QtXmlToSphinx::handleUselessTag); m_handlerMap.insert("definition", &QtXmlToSphinx::handleUselessTag); m_handlerMap.insert("printuntil", &QtXmlToSphinx::handleUselessTag); m_handlerMap.insert("relation", &QtXmlToSphinx::handleUselessTag); // Doxygen tags m_handlerMap.insert("title", &QtXmlToSphinx::handleHeadingTag); m_handlerMap.insert("ref", &QtXmlToSphinx::handleParaTag); m_handlerMap.insert("computeroutput", &QtXmlToSphinx::handleParaTag); m_handlerMap.insert("detaileddescription", &QtXmlToSphinx::handleParaTag); m_handlerMap.insert("name", &QtXmlToSphinx::handleParaTag); m_handlerMap.insert("listitem", &QtXmlToSphinx::handleItemTag); m_handlerMap.insert("parametername", &QtXmlToSphinx::handleItemTag); m_handlerMap.insert("parameteritem", &QtXmlToSphinx::handleItemTag); m_handlerMap.insert("ulink", &QtXmlToSphinx::handleLinkTag); m_handlerMap.insert("itemizedlist", &QtXmlToSphinx::handleListTag); m_handlerMap.insert("parameternamelist", &QtXmlToSphinx::handleListTag); m_handlerMap.insert("parameterlist", &QtXmlToSphinx::handleListTag); // Doxygen ignored tags m_handlerMap.insert("highlight", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("linebreak", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("programlisting", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("xreftitle", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("sp", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("entry", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("simplesect", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("verbatim", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("xrefsect", &QtXmlToSphinx::handleIgnoredTag); m_handlerMap.insert("xrefdescription", &QtXmlToSphinx::handleIgnoredTag); m_result = transform(doc); } void QtXmlToSphinx::pushOutputBuffer() { QString* buffer = new QString(); m_buffers << buffer; m_output.setString(buffer); } QString QtXmlToSphinx::popOutputBuffer() { Q_ASSERT(!m_buffers.isEmpty()); QString* str = m_buffers.pop(); QString strcpy(*str); delete str; m_output.setString(m_buffers.isEmpty() ? 0 : m_buffers.top()); return strcpy; } QString QtXmlToSphinx::expandFunction(const QString& function) { QStringList functionSpec = function.split('.'); QString className = functionSpec.first(); const AbstractMetaClass* metaClass = 0; foreach (const AbstractMetaClass* cls, m_generator->classes()) { if (cls->name() == className) { metaClass = cls; break; } } if (metaClass) { functionSpec.removeFirst(); return metaClass->typeEntry()->qualifiedTargetLangName() + "." + functionSpec.join("."); } else { return function; } } QString QtXmlToSphinx::resolveContextForMethod(const QString& methodName) { QString currentClass = m_context.split(".").last(); const AbstractMetaClass* metaClass = 0; foreach (const AbstractMetaClass* cls, m_generator->classes()) { if (cls->name() == currentClass) { metaClass = cls; break; } } if (metaClass) { QList funcList; foreach (const AbstractMetaFunction* func, metaClass->queryFunctionsByName(methodName)) { if (methodName == func->name()) funcList.append(func); } const AbstractMetaClass* implementingClass = 0; foreach (const AbstractMetaFunction* func, funcList) { implementingClass = func->implementingClass(); if (implementingClass->name() == currentClass) break; } if (implementingClass) return implementingClass->typeEntry()->qualifiedTargetLangName(); } return QLatin1String("~") + m_context; } QString QtXmlToSphinx::transform(const QString& doc) { Q_ASSERT(m_buffers.isEmpty()); Indentation indentation(INDENT); if (doc.trimmed().isEmpty()) return doc; pushOutputBuffer(); QXmlStreamReader reader(doc); while (!reader.atEnd()) { QXmlStreamReader::TokenType token = reader.readNext(); if (reader.hasError()) { m_output << INDENT << "XML Error: " + reader.errorString() + "\n" + doc; ReportHandler::warning("XML Error: " + reader.errorString() + "\n" + doc); break; } if (token == QXmlStreamReader::StartElement) { QStringRef tagName = reader.name(); TagHandler handler = m_handlerMap.value(tagName.toString(), &QtXmlToSphinx::handleUnknownTag); if (!m_handlers.isEmpty() && ( (m_handlers.top() == &QtXmlToSphinx::handleIgnoredTag) || (m_handlers.top() == &QtXmlToSphinx::handleRawTag)) ) handler = &QtXmlToSphinx::handleIgnoredTag; m_handlers.push(handler); } if (!m_handlers.isEmpty()) (this->*(m_handlers.top()))(reader); if (token == QXmlStreamReader::EndElement) { m_handlers.pop(); m_lastTagName = reader.name().toString(); } } m_output.flush(); QString retval = popOutputBuffer(); Q_ASSERT(m_buffers.isEmpty()); return retval; } QString QtXmlToSphinx::readFromLocations(const QStringList& locations, const QString& path, const QString& identifier) { QString result; bool ok; foreach (QString location, locations) { location.append('/'); location.append(path); result = readFromLocation(location, identifier, &ok); if (ok) break; } if (!ok) ReportHandler::warning("Couldn't read code snippet file: {"+ locations.join("|") + '}' + path); return result; } QString QtXmlToSphinx::readFromLocation(const QString& location, const QString& identifier, bool* ok) { QFile inputFile; inputFile.setFileName(location); if (!inputFile.open(QIODevice::ReadOnly)) { if (!ok) ReportHandler::warning("Couldn't read code snippet file: "+inputFile.fileName()); else *ok = false; return QString(); } QRegExp searchString("//!\\s*\\[" + identifier + "\\]"); QRegExp codeSnippetCode("//!\\s*\\[[\\w\\d\\s]+\\]"); QString code; QString line; bool identifierIsEmpty = identifier.isEmpty(); bool getCode = false; while (!inputFile.atEnd()) { line = inputFile.readLine(); if (identifierIsEmpty) { code += line; } else if (getCode && !line.contains(searchString)) { code += line.replace(codeSnippetCode, ""); } else if (line.contains(searchString)) { if (getCode) break; else getCode = true; } } if (!identifierIsEmpty && !getCode) ReportHandler::warning("Code snippet file found ("+location+"), but snippet "+ identifier +" not found."); if (ok) *ok = true; return code; } void QtXmlToSphinx::handleHeadingTag(QXmlStreamReader& reader) { static QString heading; static char type; static char types[] = { '-', '^' }; QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { uint typeIdx = reader.attributes().value("level").toString().toInt(); if (typeIdx >= sizeof(types)) type = types[sizeof(types)-1]; else type = types[typeIdx]; } else if (token == QXmlStreamReader::EndElement) { m_output << createRepeatedChar(heading.length(), type) << endl << endl; } else if (token == QXmlStreamReader::Characters) { heading = escape(reader.text()).trimmed(); m_output << endl << endl << heading << endl; } } void QtXmlToSphinx::handleParaTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { pushOutputBuffer(); } else if (token == QXmlStreamReader::EndElement) { QString result = popOutputBuffer().simplified(); if (result.startsWith("**Warning:**")) result.replace(0, 12, ".. warning:: "); else if (result.startsWith("**Note:**")) result.replace(0, 9, ".. note:: "); m_output << INDENT << result << endl << endl; } else if (token == QXmlStreamReader::Characters) { QString text = escape(reader.text()); if (!m_output.string()->isEmpty()) { QChar start = text[0]; QChar end = m_output.string()->at(m_output.string()->length() - 1); if ((end == '*' || end == '`') && start != ' ' && !start.isPunct()) m_output << '\\'; } m_output << INDENT << text; } } void QtXmlToSphinx::handleItalicTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement || token == QXmlStreamReader::EndElement) { m_insideItalic = !m_insideItalic; m_output << '*'; } else if (token == QXmlStreamReader::Characters) { m_output << escape(reader.text()).trimmed(); } } void QtXmlToSphinx::handleBoldTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement || token == QXmlStreamReader::EndElement) { m_insideBold = !m_insideBold; m_output << "**"; } else if (token == QXmlStreamReader::Characters) { m_output << escape(reader.text()).trimmed(); } } void QtXmlToSphinx::handleArgumentTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement || token == QXmlStreamReader::EndElement) m_output << "``"; else if (token == QXmlStreamReader::Characters) m_output << reader.text().toString().trimmed(); } void QtXmlToSphinx::handleSeeAlsoTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) m_output << INDENT << ".. seealso:: "; else if (token == QXmlStreamReader::EndElement) m_output << endl; } void QtXmlToSphinx::handleSnippetTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { bool consecutiveSnippet = m_lastTagName == "snippet" || m_lastTagName == "dots" || m_lastTagName == "codeline"; if (consecutiveSnippet) { m_output.flush(); m_output.string()->chop(2); } QString location = reader.attributes().value("location").toString(); QString identifier = reader.attributes().value("identifier").toString(); QString code = readFromLocations(m_generator->codeSnippetDirs(), location, identifier); if (!consecutiveSnippet) m_output << INDENT << "::\n\n"; Indentation indentation(INDENT); if (code.isEmpty()) { m_output << INDENT << "" << endl; } else { foreach (QString line, code.split("\n")) { if (!QString(line).trimmed().isEmpty()) m_output << INDENT << line; m_output << endl; } } m_output << endl; } } void QtXmlToSphinx::handleDotsTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { bool consecutiveSnippet = m_lastTagName == "snippet" || m_lastTagName == "dots" || m_lastTagName == "codeline"; if (consecutiveSnippet) { m_output.flush(); m_output.string()->chop(2); } Indentation indentation(INDENT); pushOutputBuffer(); m_output << INDENT; int indent = reader.attributes().value("indent").toString().toInt(); for (int i = 0; i < indent; ++i) m_output << ' '; } else if (token == QXmlStreamReader::Characters) { m_output << reader.text().toString(); } else if (token == QXmlStreamReader::EndElement) { m_output << popOutputBuffer() << "\n\n\n"; } } void QtXmlToSphinx::handleTableTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { m_currentTable.clear(); m_tableHasHeader = false; } else if (token == QXmlStreamReader::EndElement) { // write the table on m_output m_currentTable.enableHeader(m_tableHasHeader); m_currentTable.normalize(); m_output << m_currentTable; m_currentTable.clear(); } } void QtXmlToSphinx::handleTermTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { pushOutputBuffer(); } else if (token == QXmlStreamReader::Characters) { m_output << reader.text().toString().replace("::", "."); } else if (token == QXmlStreamReader::EndElement) { TableCell cell; cell.data = popOutputBuffer().trimmed(); m_currentTable << (TableRow() << cell); } } void QtXmlToSphinx::handleItemTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { if (m_currentTable.isEmpty()) m_currentTable << TableRow(); TableRow& row = m_currentTable.last(); TableCell cell; cell.colSpan = reader.attributes().value("colspan").toString().toShort(); cell.rowSpan = reader.attributes().value("rowspan").toString().toShort(); row << cell; pushOutputBuffer(); } else if (token == QXmlStreamReader::EndElement) { QString data = popOutputBuffer().trimmed(); if (!m_currentTable.isEmpty()) { TableRow& row = m_currentTable.last(); if (!row.isEmpty()) row.last().data = data; } } } void QtXmlToSphinx::handleRowTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { m_tableHasHeader = reader.name() == "header"; m_currentTable << TableRow(); } } void QtXmlToSphinx::handleListTag(QXmlStreamReader& reader) { // BUG We do not support a list inside a table cell static QString listType; QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { listType = reader.attributes().value("type").toString(); if (listType == "enum") { m_currentTable << (TableRow() << "Constant" << "Description"); m_tableHasHeader = true; } INDENT.indent--; } else if (token == QXmlStreamReader::EndElement) { INDENT.indent++; if (!m_currentTable.isEmpty()) { if (listType == "bullet") { m_output << endl; foreach (TableCell cell, m_currentTable.first()) { QStringList itemLines = cell.data.split('\n'); m_output << INDENT << "* " << itemLines.first() << endl; for (int i = 1, max = itemLines.count(); i < max; ++i) m_output << INDENT << " " << itemLines[i] << endl; } m_output << endl; } else if (listType == "enum") { m_currentTable.enableHeader(m_tableHasHeader); m_currentTable.normalize(); m_output << m_currentTable; } } m_currentTable.clear(); } } void QtXmlToSphinx::handleLinkTag(QXmlStreamReader& reader) { static QString l_linktag; static QString l_linkref; static QString l_linktext; static QString l_linktagending; static QString l_type; QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { l_linktagending = "` "; if (m_insideBold) { l_linktag.prepend("**"); l_linktagending.append("**"); } else if (m_insideItalic) { l_linktag.prepend('*'); l_linktagending.append('*'); } l_type = reader.attributes().value("type").toString(); // TODO: create a flag PROPERTY-AS-FUNCTION to ask if the properties // are recognized as such or not in the binding if (l_type == "property") l_type = "function"; if (l_type == "typedef") l_type = "class"; QString linkSource; if (l_type == "function" || l_type == "class") { linkSource = "raw"; } else if (l_type == "enum") { linkSource = "enum"; } else if (l_type == "page") { linkSource = "page"; } else { linkSource = "href"; } l_linkref = reader.attributes().value(linkSource).toString(); l_linkref.replace("::", "."); l_linkref.remove("()"); if (l_type == "function" && !m_context.isEmpty()) { l_linktag = " :meth:`"; QStringList rawlinklist = l_linkref.split("."); if (rawlinklist.size() == 1 || rawlinklist.first() == m_context) { QString context = resolveContextForMethod(rawlinklist.last()); if (!l_linkref.startsWith(context)) l_linkref.prepend(context + '.'); } else { l_linkref = expandFunction(l_linkref); } } else if (l_type == "function" && m_context.isEmpty()) { l_linktag = " :func:`"; } else if (l_type == "class") { l_linktag = " :class:`"; TypeEntry* type = TypeDatabase::instance()->findType(l_linkref); if (type) { l_linkref = type->qualifiedTargetLangName(); } else { // fall back to the old heuristic if the type wasn't found. QStringList rawlinklist = l_linkref.split("."); QStringList splittedContext = m_context.split("."); if (rawlinklist.size() == 1 || rawlinklist.first() == splittedContext.last()) { splittedContext.removeLast(); l_linkref.prepend('~' + splittedContext.join(".") + '.'); } } } else if (l_type == "enum") { l_linktag = " :attr:`"; } else if (l_type == "page" && l_linkref == m_generator->moduleName()) { l_linktag = " :mod:`"; } else { l_linktag = " :ref:`"; } } else if (token == QXmlStreamReader::Characters) { QString linktext = reader.text().toString(); linktext.replace("::", "."); QString item = l_linkref.split(".").last(); if (l_linkref == linktext || (l_linkref + "()") == linktext || item == linktext || (item + "()") == linktext) l_linktext.clear(); else l_linktext = linktext + QLatin1String("<"); } else if (token == QXmlStreamReader::EndElement) { if (!l_linktext.isEmpty()) l_linktagending.prepend('>'); m_output << l_linktag << l_linktext << escape(l_linkref) << l_linktagending; } } void QtXmlToSphinx::handleImageTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { QString href = reader.attributes().value("href").toString(); QDir dir(m_generator->outputDirectory() + '/' + m_generator->packageName().replace(".", "/")); QString imgPath = dir.relativeFilePath(m_generator->libSourceDir() + "/doc/src/") + '/' + href; if (reader.name() == "image") m_output << INDENT << ".. image:: " << imgPath << endl << endl; else m_output << ".. image:: " << imgPath << ' '; } } void QtXmlToSphinx::handleRawTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { QString format = reader.attributes().value("format").toString(); m_output << INDENT << ".. raw:: " << format.toLower() << endl << endl; } else if (token == QXmlStreamReader::Characters) { QStringList lst(reader.text().toString().split("\n")); foreach(QString row, lst) m_output << INDENT << INDENT << row << endl; } else if (token == QXmlStreamReader::EndElement) { m_output << endl << endl; } } void QtXmlToSphinx::handleCodeTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { QString format = reader.attributes().value("format").toString(); m_output << INDENT << "::" << endl << endl; INDENT.indent++; } else if (token == QXmlStreamReader::Characters) { QStringList lst(reader.text().toString().split("\n")); foreach(QString row, lst) m_output << INDENT << INDENT << row << endl; } else if (token == QXmlStreamReader::EndElement) { m_output << endl << endl; INDENT.indent--; } } void QtXmlToSphinx::handleUnknownTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) ReportHandler::warning("Unknow QtDoc tag: \"" + reader.name().toString() + "\"."); } void QtXmlToSphinx::handleSuperScriptTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { m_output << " :sup:`"; pushOutputBuffer(); } else if (token == QXmlStreamReader::Characters) { m_output << reader.text().toString(); } else if (token == QXmlStreamReader::EndElement) { m_output << popOutputBuffer(); m_output << '`'; } } void QtXmlToSphinx::handleIgnoredTag(QXmlStreamReader&) { } void QtXmlToSphinx::handleUselessTag(QXmlStreamReader&) { // Tag "description" just marks the init of "Detailed description" title. // Tag "definition" just marks enums. We have a different way to process them. } void QtXmlToSphinx::handleAnchorTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::StartElement) { QString anchor; if (reader.attributes().hasAttribute("id")) anchor = reader.attributes().value("id").toString(); else if (reader.attributes().hasAttribute("name")) anchor = reader.attributes().value("name").toString(); if (!anchor.isEmpty() && m_opened_anchor != anchor) { m_opened_anchor = anchor; m_output << INDENT << ".. _" << m_context << "_" << anchor.toLower() << ":" << endl << endl; } } else if (token == QXmlStreamReader::EndElement) { m_opened_anchor = ""; } } void QtXmlToSphinx::handleQuoteFileTag(QXmlStreamReader& reader) { QXmlStreamReader::TokenType token = reader.tokenType(); if (token == QXmlStreamReader::Characters) { QString location = reader.text().toString(); QString identifier = ""; location.prepend(m_generator->libSourceDir() + '/'); QString code = readFromLocation(location, identifier); m_output << INDENT << "::\n\n"; Indentation indentation(INDENT); if (code.isEmpty()) { m_output << INDENT << "" << endl; } else { foreach (QString line, code.split("\n")) { if (!QString(line).trimmed().isEmpty()) m_output << INDENT << line; m_output << endl; } } m_output << endl; } } void QtXmlToSphinx::Table::normalize() { if (m_normalized || isEmpty()) return; int row; int col; QtXmlToSphinx::Table& self = *this; //QDoc3 generates tables with wrong number of columns. We have to //check and if necessary, merge the last columns. int maxCols = self.at(0).count(); // add col spans for (row = 0; row < count(); ++row) { for (col = 0; col < at(row).count(); ++col) { QtXmlToSphinx::TableCell& cell = self[row][col]; bool mergeCols = (col >= maxCols); if (cell.colSpan > 0) { QtXmlToSphinx::TableCell newCell; newCell.colSpan = -1; for (int i = 0, max = cell.colSpan-1; i < max; ++i) { self[row].insert(col+1, newCell); } cell.colSpan = 0; col++; } else if (mergeCols) { self[row][maxCols - 1].data += " " + cell.data; } } } // row spans const int numCols = first().count(); for (col = 0; col < numCols; ++col) { for (row = 0; row < count(); ++row) { if (col < self[row].count()) { QtXmlToSphinx::TableCell& cell = self[row][col]; if (cell.rowSpan > 0) { QtXmlToSphinx::TableCell newCell; newCell.rowSpan = -1; int max = std::min(cell.rowSpan - 1, count()); cell.rowSpan = 0; for (int i = 0; i < max; ++i) { self[row+i+1].insert(col, newCell); } row++; } } } } m_normalized = true; } QTextStream& operator<<(QTextStream& s, const QtXmlToSphinx::Table &table) { if (table.isEmpty()) return s; if (!table.isNormalized()) { ReportHandler::warning("Attempt to print an unnormalized table!"); return s; } // calc width and height of each column and row QVector colWidths(table.first().count()); QVector rowHeights(table.count()); for (int i = 0, maxI = table.count(); i < maxI; ++i) { const QtXmlToSphinx::TableRow& row = table[i]; for (int j = 0, maxJ = std::min(row.count(), colWidths.size()); j < maxJ; ++j) { QStringList rowLines = row[j].data.split('\n'); // cache this would be a good idea foreach (QString str, rowLines) colWidths[j] = std::max(colWidths[j], str.count()); rowHeights[i] = std::max(rowHeights[i], row[j].data.count('\n') + 1); } } if (!*std::max_element(colWidths.begin(), colWidths.end())) return s; // empty table (table with empty cells) // create a horizontal line to be used later. QString horizontalLine("+"); for (int i = 0, max = colWidths.count(); i < max; ++i) { horizontalLine += createRepeatedChar(colWidths[i], '-'); horizontalLine += '+'; } // write table rows for (int i = 0, maxI = table.count(); i < maxI; ++i) { // for each row const QtXmlToSphinx::TableRow& row = table[i]; // print line s << INDENT << '+'; for (int col = 0, max = colWidths.count(); col < max; ++col) { char c; if (col >= row.length() || row[col].rowSpan == -1) c = ' '; else if (i == 1 && table.hasHeader()) c = '='; else c = '-'; s << createRepeatedChar(colWidths[col], c) << '+'; } s << endl; // Print the table cells for (int rowLine = 0; rowLine < rowHeights[i]; ++rowLine) { // for each line in a row for (int j = 0, maxJ = std::min(row.count(), colWidths.size()); j < maxJ; ++j) { // for each column const QtXmlToSphinx::TableCell& cell = row[j]; QStringList rowLines = cell.data.split('\n'); // FIXME: Cache this!!! if (!j) // First column, so we need print the identation s << INDENT; if (!j || !cell.colSpan) s << '|'; else s << ' '; s << qSetFieldWidth(colWidths[j]) << left; s << (rowLine < rowLines.count() ? rowLines[rowLine] : ""); s << qSetFieldWidth(0); } s << '|' << endl; } } s << INDENT << horizontalLine << endl; s << endl; return s; } static QString getFuncName(const AbstractMetaFunction* cppFunc) { static bool hashInitialized = false; static QHash operatorsHash; if (!hashInitialized) { operatorsHash.insert("operator+", "__add__"); operatorsHash.insert("operator+=", "__iadd__"); operatorsHash.insert("operator-", "__sub__"); operatorsHash.insert("operator-=", "__isub__"); operatorsHash.insert("operator*", "__mul__"); operatorsHash.insert("operator*=", "__imul__"); operatorsHash.insert("operator/", "__div__"); operatorsHash.insert("operator/=", "__idiv__"); operatorsHash.insert("operator%", "__mod__"); operatorsHash.insert("operator%=", "__imod__"); operatorsHash.insert("operator<<", "__lshift__"); operatorsHash.insert("operator<<=", "__ilshift__"); operatorsHash.insert("operator>>", "__rshift__"); operatorsHash.insert("operator>>=", "__irshift__"); operatorsHash.insert("operator&", "__and__"); operatorsHash.insert("operator&=", "__iand__"); operatorsHash.insert("operator|", "__or__"); operatorsHash.insert("operator|=", "__ior__"); operatorsHash.insert("operator^", "__xor__"); operatorsHash.insert("operator^=", "__ixor__"); operatorsHash.insert("operator==", "__eq__"); operatorsHash.insert("operator!=", "__ne__"); operatorsHash.insert("operator<", "__lt__"); operatorsHash.insert("operator<=", "__le__"); operatorsHash.insert("operator>", "__gt__"); operatorsHash.insert("operator>=", "__ge__"); hashInitialized = true; } QHash::const_iterator it = operatorsHash.find(cppFunc->name()); QString result = it != operatorsHash.end() ? it.value() : cppFunc->name(); return result.replace("::", "."); } QtDocGenerator::QtDocGenerator() : m_docParser(0) { } QtDocGenerator::~QtDocGenerator() { delete m_docParser; } QString QtDocGenerator::fileNameForClass(const AbstractMetaClass* cppClass) const { return QString("%1.rst").arg(getClassTargetFullName(cppClass, false)); } void QtDocGenerator::writeFormatedText(QTextStream& s, const Documentation& doc, const AbstractMetaClass* metaClass) { QString metaClassName; if (metaClass) metaClassName = getClassTargetFullName(metaClass); if (doc.format() == Documentation::Native) { QtXmlToSphinx x(this, doc.value(), metaClassName); s << x; } else { QStringList lines = doc.value().split("\n"); QRegExp regex("\\S"); // non-space character int typesystemIndentation = std::numeric_limits().max(); // check how many spaces must be removed from the begining of each line foreach (QString line, lines) { int idx = line.indexOf(regex); if (idx >= 0) typesystemIndentation = qMin(typesystemIndentation, idx); } foreach (QString line, lines) s << INDENT << line.remove(0, typesystemIndentation) << endl; } s << endl; } static void writeInheritedByList(QTextStream& s, const AbstractMetaClass* metaClass, const AbstractMetaClassList& allClasses) { AbstractMetaClassList res; foreach (AbstractMetaClass* c, allClasses) { if (c != metaClass && c->inheritsFrom(metaClass)) res << c; } if (res.isEmpty()) return; s << "**Inherited by:** "; QStringList classes; foreach (AbstractMetaClass* c, res) classes << QString(":ref:`%1`").arg(getClassTargetFullName(c, false)); s << classes.join(", ") << endl << endl; } void QtDocGenerator::generateClass(QTextStream& s, const AbstractMetaClass* metaClass) { ReportHandler::debugSparse("Generating Documentation for " + metaClass->fullName()); m_packages[metaClass->package()] << fileNameForClass(metaClass); m_docParser->setPackageName(metaClass->package()); m_docParser->fillDocumentation(const_cast(metaClass)); s << ".. module:: " << metaClass->package() << endl; QString className = getClassTargetFullName(metaClass, false); s << ".. _" << className << ":" << endl << endl; s << className << endl; s << createRepeatedChar(className.count(), '*') << endl << endl; s << ".. inheritance-diagram:: " << className << endl << " :parts: 2" << endl << endl; // TODO: This would be a parameter in the future... writeInheritedByList(s, metaClass, classes()); if (metaClass->typeEntry() && (metaClass->typeEntry()->version() != 0)) s << ".. note:: This class was introduced in Qt " << metaClass->typeEntry()->version() << endl; writeFunctionList(s, metaClass); //Function list AbstractMetaFunctionList functionList = metaClass->functions(); qSort(functionList.begin(), functionList.end(), functionSort); s << "Detailed Description\n" "--------------------\n\n"; writeInjectDocumentation(s, DocModification::Prepend, metaClass, 0); if (!writeInjectDocumentation(s, DocModification::Replace, metaClass, 0)) writeFormatedText(s, metaClass->documentation(), metaClass); if (!metaClass->isNamespace()) writeConstructors(s, metaClass); writeEnums(s, metaClass); if (!metaClass->isNamespace()) writeFields(s, metaClass); foreach (AbstractMetaFunction* func, functionList) { if (shouldSkip(func)) continue; if (func->isStatic()) s << ".. staticmethod:: "; else s << ".. method:: "; writeFunction(s, true, metaClass, func); } writeInjectDocumentation(s, DocModification::Append, metaClass, 0); } void QtDocGenerator::writeFunctionList(QTextStream& s, const AbstractMetaClass* cppClass) { QStringList functionList; QStringList virtualList; QStringList signalList; QStringList slotList; QStringList staticFunctionList; foreach (AbstractMetaFunction* func, cppClass->functions()) { if (shouldSkip(func)) continue; QString className; if (!func->isConstructor()) className = getClassTargetFullName(cppClass) + '.'; else if (func->implementingClass() && func->implementingClass()->enclosingClass()) className = getClassTargetFullName(func->implementingClass()->enclosingClass()) + '.'; QString funcName = getFuncName(func); QString str("def :meth:`"); str += funcName; str += '<'; if (!funcName.startsWith(className)) str += className; str += funcName; str += ">` ("; str += parseArgDocStyle(cppClass, func); str += ')'; if (func->isStatic()) staticFunctionList << str; else if (func->isVirtual()) virtualList << str; else if (func->isSignal()) signalList << str; else if (func->isSlot()) slotList << str; else functionList << str; } if ((functionList.size() > 0) || (staticFunctionList.size() > 0)) { QtXmlToSphinx::Table functionTable; QtXmlToSphinx::TableRow row; s << "Synopsis" << endl << "--------" << endl << endl; writeFunctionBlock(s, "Functions", functionList); writeFunctionBlock(s, "Virtual functions", virtualList); writeFunctionBlock(s, "Slots", slotList); writeFunctionBlock(s, "Signals", signalList); writeFunctionBlock(s, "Static functions", staticFunctionList); } } void QtDocGenerator::writeFunctionBlock(QTextStream& s, const QString& title, QStringList& functions) { if (functions.size() > 0) { s << title << endl << QString('^').repeated(title.size()) << endl; qSort(functions); s << ".. container:: function_list" << endl << endl; Indentation indentation(INDENT); foreach (QString func, functions) s << '*' << INDENT << func << endl; s << endl << endl; } } void QtDocGenerator::writeEnums(QTextStream& s, const AbstractMetaClass* cppClass) { static const QString section_title(".. attribute:: "); foreach (AbstractMetaEnum* en, cppClass->enums()) { s << section_title << getClassTargetFullName(cppClass) << "." << en->name() << endl << endl; writeFormatedText(s, en->documentation(), cppClass); if (en->typeEntry() && (en->typeEntry()->version() != 0)) s << ".. note:: This enum was introduced or modified in Qt " << en->typeEntry()->version() << endl; } } void QtDocGenerator::writeFields(QTextStream& s, const AbstractMetaClass* cppClass) { static const QString section_title(".. attribute:: "); foreach (AbstractMetaField* field, cppClass->fields()) { s << section_title << getClassTargetFullName(cppClass) << "." << field->name() << endl << endl; //TODO: request for member ‘documentation’ is ambiguous writeFormatedText(s, field->AbstractMetaAttributes::documentation(), cppClass); } } void QtDocGenerator::writeConstructors(QTextStream& s, const AbstractMetaClass* cppClass) { static const QString sectionTitle = ".. class:: "; static const QString sectionTitleSpace = QString(sectionTitle.size(), ' '); AbstractMetaFunctionList lst = cppClass->queryFunctions(AbstractMetaClass::Constructors | AbstractMetaClass::Visible); bool first = true; QHash arg_map; foreach(AbstractMetaFunction* func, lst) { if (func->isModifiedRemoved()) continue; if (first) { first = false; s << sectionTitle; } else { s << sectionTitleSpace; } writeFunction(s, false, cppClass, func); foreach(AbstractMetaArgument* arg, func->arguments()) { if (!arg_map.contains(arg->name())) { arg_map.insert(arg->name(), arg); } } } s << endl; foreach (AbstractMetaArgument* arg, arg_map.values()) { Indentation indentation(INDENT); writeParamerteType(s, cppClass, arg); } s << endl; foreach (AbstractMetaFunction* func, lst) { writeFormatedText(s, func->documentation(), cppClass); } } QString QtDocGenerator::parseArgDocStyle(const AbstractMetaClass* cppClass, const AbstractMetaFunction* func) { QString ret; int optArgs = 0; foreach (AbstractMetaArgument* arg, func->arguments()) { if (func->argumentRemoved(arg->argumentIndex() + 1)) continue; bool thisIsoptional = !arg->defaultValueExpression().isEmpty(); if (optArgs || thisIsoptional) { ret += '['; optArgs++; } if (arg->argumentIndex() > 0) ret += ", "; ret += arg->name(); if (thisIsoptional) { QString defValue = arg->defaultValueExpression(); if (defValue == "QString()") { defValue = "\"\""; } else if (defValue == "QStringList()" || defValue.startsWith("QVector") || defValue.startsWith("QList")) { defValue = "list()"; } else if (defValue == "QVariant()") { defValue = "None"; } else { defValue.replace("::", "."); if (defValue == "0" && (arg->type()->isQObject() || arg->type()->isObject())) defValue = "None"; } ret += "=" + defValue; } } ret += QString(']').repeated(optArgs); return ret; } void QtDocGenerator::writeDocSnips(QTextStream &s, const CodeSnipList &codeSnips, CodeSnip::Position position, TypeSystem::Language language) { Indentation indentation(INDENT); QStringList invalidStrings; const static QString startMarkup("[sphinx-begin]"); const static QString endMarkup("[sphinx-end]"); invalidStrings << "*" << "//" << "/*" << "*/"; foreach (CodeSnip snip, codeSnips) { if ((snip.position != position) || !(snip.language & language)) continue; QString code = snip.code(); while (code.contains(startMarkup) && code.contains(endMarkup)) { int startBlock = code.indexOf(startMarkup) + startMarkup.size(); int endBlock = code.indexOf(endMarkup); if ((startBlock == -1) || (endBlock == -1)) break; QString codeBlock = code.mid(startBlock, endBlock - startBlock); QStringList rows = codeBlock.split("\n"); int currenRow = 0; int offset = 0; foreach(QString row, rows) { foreach(QString invalidString, invalidStrings) { row = row.remove(invalidString); } if (row.trimmed().size() == 0) { if (currenRow == 0) continue; else s << endl; } if (currenRow == 0) { //find offset for (int i=0, i_max = row.size(); i < i_max; i++) { if (row[i] == ' ') offset++; else if (row[i] == '\n') offset = 0; else break; } } row = row.mid(offset); s << row << endl; currenRow++; } code = code.mid(endBlock+endMarkup.size()); } } } bool QtDocGenerator::writeInjectDocumentation(QTextStream& s, DocModification::Mode mode, const AbstractMetaClass* cppClass, const AbstractMetaFunction* func) { Indentation indentation(INDENT); bool didSomething = false; foreach (DocModification mod, cppClass->typeEntry()->docModifications()) { if (mod.mode() == mode) { bool modOk = func ? mod.signature() == func->minimalSignature() : mod.signature().isEmpty(); if (modOk) { Documentation doc; Documentation::Format fmt; if (mod.format == TypeSystem::NativeCode) fmt = Documentation::Native; else if (mod.format == TypeSystem::TargetLangCode) fmt = Documentation::Target; else continue; doc.setValue(mod.code() , fmt); writeFormatedText(s, doc, cppClass); didSomething = true; } } } s << endl; // TODO: Deprecate the use of doc string on glue code. // This is pre "add-function" and "inject-documentation" tags. if (func) { writeDocSnips(s, func->injectedCodeSnips(), (mode == DocModification::Prepend ? CodeSnip::Beginning : CodeSnip::End), TypeSystem::TargetLangCode); } else { writeDocSnips(s, cppClass->typeEntry()->codeSnips(), (mode == DocModification::Prepend ? CodeSnip::Beginning : CodeSnip::End), TypeSystem::TargetLangCode); } return didSomething; } void QtDocGenerator::writeFunctionSignature(QTextStream& s, const AbstractMetaClass* cppClass, const AbstractMetaFunction* func) { QString className; if (!func->isConstructor()) className = getClassTargetFullName(cppClass) + '.'; else if (func->implementingClass() && func->implementingClass()->enclosingClass()) className = getClassTargetFullName(func->implementingClass()->enclosingClass()) + '.'; QString funcName = getFuncName(func); if (!funcName.startsWith(className)) funcName = className + funcName; s << funcName << "(" << parseArgDocStyle(cppClass, func) << ")"; } QString QtDocGenerator::translateToPythonType(const AbstractMetaType* type, const AbstractMetaClass* cppClass) { QString strType; if (type->name() == "QString") { strType = "unicode"; } else if (type->name() == "QVariant") { strType = "object"; } else if (type->name() == "QStringList") { strType = "list of strings"; } else if (type->isConstant() && type->name() == "char" && type->indirections() == 1) { strType = "str"; } else if (type->name().startsWith("unsigned short")) { strType = "int"; } else if (type->name().startsWith("unsigned ")) { // uint and ulong strType = "long"; } else if (type->isContainer()) { QString strType = translateType(type, cppClass, Options(ExcludeConst) | ExcludeReference); strType.remove("*"); strType.remove(">"); strType.remove("<"); strType.replace("::", "."); if (strType.contains("QList") || strType.contains("QVector")) { strType.replace("QList", "list of "); strType.replace("QVector", "list of "); } else if (strType.contains("QHash") || strType.contains("QMap")) { strType.remove("QHash"); strType.remove("QMap"); QStringList types = strType.split(","); strType = QString("Dictionary with keys of type %1 and values of type %2.") .arg(types[0]).arg(types[1]); } } else { QString refTag; if (type->isEnum()) refTag = "attr"; else refTag = "class"; strType = ':' + refTag + ":`" + type->fullName() + '`'; } return strType; } void QtDocGenerator::writeParamerteType(QTextStream& s, const AbstractMetaClass* cppClass, const AbstractMetaArgument* arg) { s << INDENT << ":param " << arg->name() << ": " << translateToPythonType(arg->type(), cppClass) << endl; } void QtDocGenerator::writeFunctionParametersType(QTextStream& s, const AbstractMetaClass* cppClass, const AbstractMetaFunction* func) { Indentation indentation(INDENT); s << endl; foreach (AbstractMetaArgument* arg, func->arguments()) { if (func->argumentRemoved(arg->argumentIndex() + 1)) continue; writeParamerteType(s, cppClass, arg); } if (!func->isConstructor() && func->type()) { QString retType; // check if the return type was modified foreach (FunctionModification mod, func->modifications()) { foreach (ArgumentModification argMod, mod.argument_mods) { if (argMod.index == 0) { retType = argMod.modified_type; break; } } } if (retType.isEmpty()) retType = translateToPythonType(func->type(), cppClass); s << INDENT << ":rtype: " << retType << endl; } s << endl; } void QtDocGenerator::writeFunction(QTextStream& s, bool writeDoc, const AbstractMetaClass* cppClass, const AbstractMetaFunction* func) { writeFunctionSignature(s, cppClass, func); s << endl; if (func->typeEntry() && (func->typeEntry()->version() != 0)) s << ".. note:: This method was introduced in Qt " << func->typeEntry()->version() << endl; if (writeDoc) { s << endl; writeFunctionParametersType(s, cppClass, func); s << endl; writeInjectDocumentation(s, DocModification::Prepend, cppClass, func); if (!writeInjectDocumentation(s, DocModification::Replace, cppClass, func)) writeFormatedText(s, func->documentation(), cppClass); writeInjectDocumentation(s, DocModification::Append, cppClass, func); } } static void writeFancyToc(QTextStream& s, const QStringList& items, int cols = 4) { typedef QMap TocMap; TocMap tocMap; QChar Q('Q'); QChar idx; foreach (QString item, items) { if (item.isEmpty()) continue; if (item.startsWith(Q) && item.length() > 1) idx = item[1]; item.chop(4); // Remove the .rst extension tocMap[idx] << item; } QtXmlToSphinx::Table table; QtXmlToSphinx::TableRow row; int itemsPerCol = (items.size() + tocMap.size()*2) / cols; QString currentColData; int i = 0; QTextStream ss(¤tColData); QMutableMapIterator it(tocMap); while (it.hasNext()) { it.next(); qSort(it.value()); if (i) ss << endl; ss << "**" << it.key() << "**" << endl << endl; i += 2; // a letter title is equivalent to two entries in space foreach (QString item, it.value()) { ss << "* :doc:`" << item << "`" << endl; ++i; // end of column detected! if (i > itemsPerCol) { ss.flush(); QtXmlToSphinx::TableCell cell(currentColData); row << cell; currentColData.clear(); i = 0; } } } if (i) { ss.flush(); QtXmlToSphinx::TableCell cell(currentColData); row << cell; currentColData.clear(); i = 0; } table << row; table.normalize(); s << ".. container:: pysidetoc" << endl << endl; s << table; } void QtDocGenerator::finishGeneration() { if (classes().isEmpty()) return; QMap::iterator it = m_packages.begin(); for (; it != m_packages.end(); ++it) { QString outputDir = outputDirectory() + '/' + QString(it.key()).replace(".", "/"); FileOut output(outputDir + "/index.rst"); QTextStream& s = output.stream; s << ".. module:: " << it.key() << endl << endl; QString title = it.key(); s << title << endl; s << createRepeatedChar(title.length(), '*') << endl << endl; /* Avoid showing "Detailed Description for *every* class in toc tree */ Indentation indentation(INDENT); // Search for extra-sections if (!m_extraSectionDir.isEmpty()) { QDir extraSectionDir(m_extraSectionDir); QStringList fileList = extraSectionDir.entryList(QStringList() << (it.key() + "?*.rst"), QDir::Files); QStringList::iterator it2 = fileList.begin(); for (; it2 != fileList.end(); ++it2) { QString origFileName(*it2); it2->remove(0, it.key().count() + 1); QString newFilePath = outputDir + '/' + *it2; if (QFile::exists(newFilePath)) QFile::remove(newFilePath); if (!QFile::copy(m_extraSectionDir + '/' + origFileName, newFilePath)) { ReportHandler::warning("Error copying extra doc " + (m_extraSectionDir + '/' + origFileName) + " to " + newFilePath); } } it.value().append(fileList); } writeFancyToc(s, it.value()); s << INDENT << ".. container:: hide" << endl << endl; { Indentation indentation(INDENT); s << INDENT << ".. toctree::" << endl; Indentation deeperIndentation(INDENT); s << INDENT << ":maxdepth: 1" << endl << endl; foreach (QString className, it.value()) s << INDENT << className << endl; s << endl << endl; } s << "Detailed Description" << endl; s << "--------------------" << endl << endl; // module doc is always wrong and C++istic, so go straight to the extra directory! QFile moduleDoc(m_extraSectionDir + '/' + it.key() + ".rst"); if (moduleDoc.open(QIODevice::ReadOnly | QIODevice::Text)) { s << moduleDoc.readAll(); moduleDoc.close(); } else { // try the normal way Documentation moduleDoc = m_docParser->retrieveModuleDocumentation(it.key()); if (moduleDoc.format() == Documentation::Native) { QtXmlToSphinx x(this, moduleDoc.value(), QString(it.key()).remove(0, it.key().lastIndexOf('.') + 1)); s << x; } else { s << moduleDoc.value(); } } } } bool QtDocGenerator::doSetup(const QMap& args) { m_libSourceDir = args.value("library-source-dir"); m_docDataDir = args.value("documentation-data-dir"); #ifdef __WIN32__ # define PATH_SEP ";" #else # define PATH_SEP ":" #endif m_codeSnippetDirs = args.value("documentation-code-snippets-dir", m_libSourceDir).split(PATH_SEP); m_extraSectionDir = args.value("documentation-extra-sections-dir"); m_docParser = args.value("doc-parser") == "doxygen" ? reinterpret_cast(new DoxygenParser) : reinterpret_cast(new QtDocParser); ReportHandler::warning("doc-parser: " + args.value("doc-parser")); if (m_libSourceDir.isEmpty() || m_docDataDir.isEmpty()) { ReportHandler::warning("Documentation data dir and/or Qt source dir not informed, " "documentation will not be extracted from Qt sources."); return false; } else { m_docParser->setDocumentationDataDirectory(m_docDataDir); m_docParser->setLibrarySourceDirectory(m_libSourceDir); } return true; } QMap QtDocGenerator::options() const { QMap options; options.insert("doc-parser", "The documentation parser used to interpret the documentaion input files (qdoc3|doxygen)"); options.insert("library-source-dir", "Directory where library source code is located"); options.insert("documentation-data-dir", "Directory with XML files generated by documentation tool (qdoc3 or Doxygen)"); options.insert("documentation-code-snippets-dir", "Directory used to search code snippets used by the documentation"); options.insert("documentation-extra-sections-dir", "Directory used to search for extra documentation sections"); return options; }