diff options
Diffstat (limited to 'tools/qdoc3/doc.cpp')
-rw-r--r-- | tools/qdoc3/doc.cpp | 4946 |
1 files changed, 4946 insertions, 0 deletions
diff --git a/tools/qdoc3/doc.cpp b/tools/qdoc3/doc.cpp new file mode 100644 index 0000000..1d3a7d4 --- /dev/null +++ b/tools/qdoc3/doc.cpp @@ -0,0 +1,4946 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "config.h" +#include "doc.h" +#include "codemarker.h" +#include "editdistance.h" +#include "openedlist.h" +#include "quoter.h" +#include "text.h" +#include "tokenizer.h" +#include <qdatetime.h> +#include <qdebug.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qhash.h> +#include <qtextstream.h> +#include <qregexp.h> +#include <ctype.h> +#include <limits.h> + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QSet<QString>, null_Set_QString) +Q_GLOBAL_STATIC(QStringList, null_QStringList) +Q_GLOBAL_STATIC(QList<Text>, null_QList_Text) +Q_GLOBAL_STATIC(QStringMap, null_QStringMap) + +struct Macro +{ + QString defaultDef; + Location defaultDefLocation; + QStringMap otherDefs; + int numParams; +}; + +enum { + CMD_A, CMD_ABSTRACT, CMD_BADCODE, CMD_BASENAME, CMD_BOLD, + CMD_BRIEF, CMD_C, CMD_CAPTION, CMD_CHAPTER, CMD_CODE, + CMD_CODELINE, CMD_DOTS, CMD_ELSE, CMD_ENDABSTRACT, + CMD_ENDCHAPTER, CMD_ENDCODE, CMD_ENDFOOTNOTE, CMD_ENDIF, + CMD_ENDLEGALESE, CMD_ENDLINK, CMD_ENDLIST, CMD_ENDOMIT, + CMD_ENDPART, CMD_ENDQUOTATION, CMD_ENDRAW, CMD_ENDSECTION1, + CMD_ENDSECTION2, CMD_ENDSECTION3, CMD_ENDSECTION4, + CMD_ENDSIDEBAR, CMD_ENDTABLE, CMD_EXPIRE, CMD_FOOTNOTE, + CMD_GENERATELIST, CMD_GRANULARITY, CMD_HEADER, CMD_I, + CMD_IF, CMD_IMAGE, CMD_INCLUDE, CMD_INLINEIMAGE, CMD_INDEX, + CMD_KEYWORD, CMD_L, CMD_LEGALESE, CMD_LINK, CMD_LIST, + CMD_META, CMD_NEWCODE, CMD_O, CMD_OLDCODE, CMD_OMIT, + CMD_OMITVALUE, CMD_OVERLOAD, + CMD_PART, CMD_PRINTLINE, CMD_PRINTTO, + CMD_PRINTUNTIL, CMD_QUOTATION, CMD_QUOTEFILE, + CMD_QUOTEFROMFILE, CMD_QUOTEFUNCTION, CMD_RAW, CMD_ROW, + CMD_SA, CMD_SECTION1, CMD_SECTION2, CMD_SECTION3, + CMD_SECTION4, CMD_SIDEBAR, CMD_SKIPLINE, CMD_SKIPTO, + CMD_SKIPUNTIL, CMD_SNIPPET, CMD_SUB, CMD_SUP, CMD_TABLE, + CMD_TABLEOFCONTENTS, CMD_TARGET, CMD_TT, CMD_UNDERLINE, + CMD_UNICODE, CMD_VALUE, CMD_WARNING, +#ifdef QDOC_QML + CMD_QML, CMD_ENDQML, CMD_CPP, CMD_ENDCPP, CMD_QMLTEXT, + CMD_ENDQMLTEXT, CMD_CPPTEXT, CMD_ENDCPPTEXT, +#endif + NOT_A_CMD +}; + +static struct { + const char *english; + int no; + QString *alias; +} cmds[] = { + { "a", CMD_A, 0 }, + { "abstract", CMD_ABSTRACT, 0 }, + { "badcode", CMD_BADCODE, 0 }, + { "basename", CMD_BASENAME, 0 }, // ### don't document for now + { "bold", CMD_BOLD, 0 }, + { "brief", CMD_BRIEF, 0 }, + { "c", CMD_C, 0 }, + { "caption", CMD_CAPTION, 0 }, + { "chapter", CMD_CHAPTER, 0 }, + { "code", CMD_CODE, 0 }, + { "codeline", CMD_CODELINE, 0}, + { "dots", CMD_DOTS, 0 }, + { "else", CMD_ELSE, 0 }, + { "endabstract", CMD_ENDABSTRACT, 0 }, + { "endchapter", CMD_ENDCHAPTER, 0 }, + { "endcode", CMD_ENDCODE, 0 }, + { "endfootnote", CMD_ENDFOOTNOTE, 0 }, + { "endif", CMD_ENDIF, 0 }, + { "endlegalese", CMD_ENDLEGALESE, 0 }, + { "endlink", CMD_ENDLINK, 0 }, + { "endlist", CMD_ENDLIST, 0 }, + { "endomit", CMD_ENDOMIT, 0 }, + { "endpart", CMD_ENDPART, 0 }, + { "endquotation", CMD_ENDQUOTATION, 0 }, + { "endraw", CMD_ENDRAW, 0 }, + { "endsection1", CMD_ENDSECTION1, 0 }, // ### don't document for now + { "endsection2", CMD_ENDSECTION2, 0 }, // ### don't document for now + { "endsection3", CMD_ENDSECTION3, 0 }, // ### don't document for now + { "endsection4", CMD_ENDSECTION4, 0 }, // ### don't document for now + { "endsidebar", CMD_ENDSIDEBAR, 0 }, + { "endtable", CMD_ENDTABLE, 0 }, + { "expire", CMD_EXPIRE, 0 }, + { "footnote", CMD_FOOTNOTE, 0 }, + { "generatelist", CMD_GENERATELIST, 0 }, + { "granularity", CMD_GRANULARITY, 0 }, // ### don't document for now + { "header", CMD_HEADER, 0 }, + { "i", CMD_I, 0 }, + { "if", CMD_IF, 0 }, + { "image", CMD_IMAGE, 0 }, + { "include", CMD_INCLUDE, 0 }, + { "inlineimage", CMD_INLINEIMAGE, 0 }, + { "index", CMD_INDEX, 0 }, // ### don't document for now + { "keyword", CMD_KEYWORD, 0 }, + { "l", CMD_L, 0 }, + { "legalese", CMD_LEGALESE, 0 }, + { "link", CMD_LINK, 0 }, + { "list", CMD_LIST, 0 }, + { "meta", CMD_META, 0 }, + { "newcode", CMD_NEWCODE, 0 }, + { "o", CMD_O, 0 }, + { "oldcode", CMD_OLDCODE, 0 }, + { "omit", CMD_OMIT, 0 }, + { "omitvalue", CMD_OMITVALUE, 0 }, + { "overload", CMD_OVERLOAD, 0 }, + { "part", CMD_PART, 0 }, + { "printline", CMD_PRINTLINE, 0 }, + { "printto", CMD_PRINTTO, 0 }, + { "printuntil", CMD_PRINTUNTIL, 0 }, + { "quotation", CMD_QUOTATION, 0 }, + { "quotefile", CMD_QUOTEFILE, 0 }, + { "quotefromfile", CMD_QUOTEFROMFILE, 0 }, + { "quotefunction", CMD_QUOTEFUNCTION, 0 }, // ### don't document for now + { "raw", CMD_RAW, 0 }, + { "row", CMD_ROW, 0 }, + { "sa", CMD_SA, 0 }, + { "section1", CMD_SECTION1, 0 }, + { "section2", CMD_SECTION2, 0 }, + { "section3", CMD_SECTION3, 0 }, + { "section4", CMD_SECTION4, 0 }, + { "sidebar", CMD_SIDEBAR, 0 }, // ### don't document for now + { "skipline", CMD_SKIPLINE, 0 }, + { "skipto", CMD_SKIPTO, 0 }, + { "skipuntil", CMD_SKIPUNTIL, 0 }, + { "snippet", CMD_SNIPPET, 0 }, + { "sub", CMD_SUB, 0 }, + { "sup", CMD_SUP, 0 }, + { "table", CMD_TABLE, 0 }, + { "tableofcontents", CMD_TABLEOFCONTENTS, 0 }, + { "target", CMD_TARGET, 0 }, + { "tt", CMD_TT, 0 }, + { "underline", CMD_UNDERLINE, 0 }, + { "unicode", CMD_UNICODE, 0 }, + { "value", CMD_VALUE, 0 }, + { "warning", CMD_WARNING, 0 }, +#ifdef QDOC_QML + { "qml", CMD_QML, 0 }, + { "endqml", CMD_ENDQML, 0 }, + { "cpp", CMD_CPP, 0 }, + { "endcpp", CMD_ENDCPP, 0 }, + { "qmltext", CMD_QMLTEXT, 0 }, + { "endqmltext", CMD_ENDQMLTEXT, 0 }, + { "cpptext", CMD_CPPTEXT, 0 }, + { "endcpptext", CMD_ENDCPPTEXT, 0 }, +#endif + { 0, 0, 0 } +}; + +typedef QHash<QString, int> QHash_QString_int; +typedef QHash<QString, Macro> QHash_QString_Macro; + +Q_GLOBAL_STATIC(QStringMap, aliasMap) +Q_GLOBAL_STATIC(QHash_QString_int, cmdHash) +Q_GLOBAL_STATIC(QHash_QString_Macro, macroHash) + +class DocPrivateExtra +{ + public: + QString baseName; + Doc::SectioningUnit granularity; + Doc::SectioningUnit sectioningUnit; // ### + QList<Atom*> tableOfContents; + QList<int> tableOfContentsLevels; + QList<Atom*> keywords; + QList<Atom*> targets; + QStringMap metaMap; + + DocPrivateExtra() + : granularity(Doc::Part) { } +}; + +struct Shared // ### get rid of +{ + Shared() + : count(1) { } + void ref() { ++count; } + bool deref() { return (--count == 0); } + + int count; +}; + +static QString cleanLink(const QString &link) +{ + int colonPos = link.indexOf(':'); + if ((colonPos == -1) || + (!link.startsWith("file:") && !link.startsWith("mailto:"))) + return link; + return link.mid(colonPos + 1).simplified(); +} + +class DocPrivate : public Shared +{ + public: + DocPrivate(const Location& start = Location::null, + const Location& end = Location::null, + const QString& source = ""); + ~DocPrivate(); + + void addAlso(const Text& also); + void constructExtra(); + bool isEnumDocSimplifiable() const; + + // ### move some of this in DocPrivateExtra + Location start_loc; + Location end_loc; + QString src; + Text text; + QSet<QString> params; + QList<Text> alsoList; + QStringList enumItemList; + QStringList omitEnumItemList; + QSet<QString> metacommandsUsed; + QCommandMap metaCommandMap; + bool hasLegalese : 1; + bool hasSectioningUnits : 1; + DocPrivateExtra *extra; +}; + +DocPrivate::DocPrivate(const Location& start, + const Location& end, + const QString& source) + : start_loc(start), + end_loc(end), + src(source), + hasLegalese(false), + hasSectioningUnits(false), + extra(0) +{ + // nothing. +} + +DocPrivate::~DocPrivate() +{ + delete extra; +} + +void DocPrivate::addAlso(const Text& also) +{ + alsoList.append(also); +} + +void DocPrivate::constructExtra() +{ + if (extra == 0) + extra = new DocPrivateExtra; +} + +bool DocPrivate::isEnumDocSimplifiable() const +{ + bool justMetColon = false; + int numValueTables = 0; + + const Atom *atom = text.firstAtom(); + while (atom) { + if (atom->type() == Atom::AutoLink || atom->type() == Atom::String) { + justMetColon = atom->string().endsWith(":"); + } else if ((atom->type() == Atom::ListLeft) && + (atom->string() == ATOM_LIST_VALUE)) { + if (justMetColon || numValueTables > 0) + return false; + ++numValueTables; + } + atom = atom->next(); + } + return true; +} + +class DocParser +{ + public: + void parse(const QString &source, + DocPrivate *docPrivate, + const QSet<QString> &metaCommandSet); + + static int endCmdFor(int cmd); + static QString cmdName(int cmd); + static QString endCmdName(int cmd); + static QString untabifyEtc(const QString& str); + static int indentLevel(const QString& str); + static QString unindent(int level, const QString& str); + static QString slashed(const QString& str); + + static int tabSize; + static QStringList exampleFiles; + static QStringList exampleDirs; + static QStringList sourceFiles; + static QStringList sourceDirs; + static bool quoting; + + private: + Location& location(); + QString detailsUnknownCommand(const QSet<QString>& metaCommandSet, + const QString& str); + void checkExpiry(const QString& date); + void insertBaseName(const QString &baseName); + void insertTarget(const QString& target, bool keyword); + void include(const QString& fileName); + void startFormat(const QString& format, int cmd); + bool openCommand(int cmd); + bool closeCommand(int endCmd); + void startSection(Doc::SectioningUnit unit, int cmd); + void endSection(int unit, int endCmd); + void parseAlso(); + void append(Atom::Type type, const QString& string = ""); + void appendChar(QChar ch); + void appendWord(const QString &word); + void appendToCode(const QString &code); + void startNewPara(); + void enterPara(Atom::Type leftType = Atom::ParaLeft, + Atom::Type rightType = Atom::ParaRight, + const QString& string = ""); + void leavePara(); + void leaveValue(); + void leaveValueList(); + void leaveTableRow(); + CodeMarker *quoteFromFile(); + void expandMacro(const QString& name, const QString& def, int numParams); + Doc::SectioningUnit getSectioningUnit(); + QString getArgument(bool verbatim = false); + QString getOptionalArgument(); + QString getRestOfLine(); + QString getMetaCommandArgument(const QString &cmdStr); + QString getUntilEnd(int cmd); + QString getCode(int cmd, CodeMarker *marker); + QString getUnmarkedCode(int cmd); + + bool isBlankLine(); + bool isLeftBraceAhead(); + void skipSpacesOnLine(); + void skipSpacesOrOneEndl(); + void skipAllSpaces(); + void skipToNextPreprocessorCommand(); + + QStack<int> openedInputs; + + QString in; + int pos; + int len; + Location cachedLoc; + int cachedPos; + + DocPrivate *priv; + enum ParaState { OutsidePara, InsideSingleLinePara, InsideMultiLinePara }; + ParaState paraState; + bool inTableHeader; + bool inTableRow; + bool inTableItem; + bool indexStartedPara; // ### rename + Atom::Type pendingParaLeftType; + Atom::Type pendingParaRightType; + QString pendingParaString; + + int braceDepth; + int minIndent; + Doc::SectioningUnit currentSectioningUnit; + QMap<QString, Location> targetMap; + QMap<int, QString> pendingFormats; + QStack<int> openedCommands; + QStack<OpenedList> openedLists; + Quoter quoter; +}; + +int DocParser::tabSize; +QStringList DocParser::exampleFiles; +QStringList DocParser::exampleDirs; +QStringList DocParser::sourceFiles; +QStringList DocParser::sourceDirs; +bool DocParser::quoting; + +/*! + Parse the \a source string to build a Text data structure + in \a docPrivate. The Text data structure is a linked list + of Atoms. + + \a metaCommandSet is the set of metacommands that may be + found in \a source. These metacommands are not markup text + commands. They are topic commands and related metacommands. + */ +void DocParser::parse(const QString& source, + DocPrivate *docPrivate, + const QSet<QString>& metaCommandSet) +{ + in = source; + pos = 0; + len = in.length(); + cachedLoc = docPrivate->start_loc; + cachedPos = 0; + priv = docPrivate; + priv->text << Atom::Nop; + + paraState = OutsidePara; + inTableHeader = false; + inTableRow = false; + inTableItem = false; + indexStartedPara = false; + pendingParaLeftType = Atom::Nop; + pendingParaRightType = Atom::Nop; + + braceDepth = 0; + minIndent = INT_MAX; + currentSectioningUnit = Doc::Book; + openedCommands.push(CMD_OMIT); + quoter.reset(); + + CodeMarker *marker = 0; + Atom *currentLinkAtom = 0; + QString x; + QStack<bool> preprocessorSkipping; + int numPreprocessorSkipping = 0; + + while (pos < len) { + QChar ch = in.at(pos); + + switch (ch.unicode()) { + case '\\': + { + QString cmdStr; + pos++; + while (pos < len) { + ch = in.at(pos); + if (ch.isLetterOrNumber()) { + cmdStr += ch; + pos++; + } else { + break; + } + } + if (cmdStr.isEmpty()) { + if (pos < len) { + enterPara(); + if (in.at(pos).isSpace()) { + skipAllSpaces(); + appendChar(QLatin1Char(' ')); + } else { + appendChar(in.at(pos++)); + } + } + } + else { + int cmd = cmdHash()->value(cmdStr,NOT_A_CMD); + switch (cmd) { + case CMD_A: + enterPara(); + x = getArgument(); + append(Atom::FormattingLeft,ATOM_FORMATTING_PARAMETER); + append(Atom::String, x); + append(Atom::FormattingRight,ATOM_FORMATTING_PARAMETER); + priv->params.insert(x); + break; + case CMD_ABSTRACT: + if (openCommand(cmd)) { + leavePara(); + append(Atom::AbstractLeft); + } + break; + case CMD_BADCODE: + leavePara(); +#ifdef QDOC2DOX + if (DoxWriter::isDoxPass()) + append(Atom::CodeBad,getUnmarkedCode(CMD_BADCODE)); + else + append(Atom::CodeBad,getCode(CMD_BADCODE, marker)); +#else + append(Atom::CodeBad,getCode(CMD_BADCODE, marker)); +#endif + break; + case CMD_BASENAME: + leavePara(); + insertBaseName(getArgument()); + break; + case CMD_BOLD: + startFormat(ATOM_FORMATTING_BOLD, cmd); + break; + case CMD_BRIEF: + leavePara(); + enterPara(Atom::BriefLeft, Atom::BriefRight); + break; + case CMD_C: + enterPara(); + x = untabifyEtc(getArgument(true)); +#ifdef QDOC2DOX + if (DoxWriter::isDoxPass()) + append(Atom::C, x); + else { + marker = CodeMarker::markerForCode(x); + append(Atom::C, marker->markedUpCode(x, 0, "")); + } +#else + marker = CodeMarker::markerForCode(x); + append(Atom::C, marker->markedUpCode(x, 0, "")); +#endif + break; + case CMD_CAPTION: + leavePara(); + /* ... */ + break; + case CMD_CHAPTER: + startSection(Doc::Chapter, cmd); + break; + case CMD_CODE: + leavePara(); +#ifdef QDOC2DOX + if (DoxWriter::isDoxPass()) + append(Atom::Code, getUnmarkedCode(CMD_CODE)); + else + append(Atom::Code, getCode(CMD_CODE, marker)); +#else + append(Atom::Code, getCode(CMD_CODE, marker)); +#endif + break; +#ifdef QDOC_QML + case CMD_QML: + leavePara(); + append(Atom::Qml, getCode(CMD_QML, marker)); + break; +#endif + case CMD_CODELINE: + { +#ifdef QDOC2DOX + if (!quoting && !DoxWriter::isDoxPass()) { + if (priv->text.lastAtom()->type() == Atom::Code + && priv->text.lastAtom()->string().endsWith("\n\n")) + priv->text.lastAtom()->chopString(); + appendToCode("\n"); + } else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, " "); + } +#else + if (!quoting) { + if (priv->text.lastAtom()->type() == Atom::Code + && priv->text.lastAtom()->string().endsWith("\n\n")) + priv->text.lastAtom()->chopString(); + appendToCode("\n"); + } else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, " "); + } +#endif + } + break; + case CMD_DOTS: + { +#ifdef QDOC2DOX + if (DoxWriter::isDoxPass()) { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, " ..."); + } + else if (!quoting) { + if (priv->text.lastAtom()->type() == Atom::Code + && priv->text.lastAtom()->string().endsWith("\n\n")) + priv->text.lastAtom()->chopString(); + + QString arg = getOptionalArgument(); + int indent = 4; + if (!arg.isEmpty()) + indent = arg.toInt(); + for (int i = 0; i < indent; ++i) + appendToCode(" "); + appendToCode("...\n"); + } + else { + append(Atom::CodeQuoteCommand, cmdStr); + QString arg = getOptionalArgument(); + if (arg.isEmpty()) + arg = "4"; + append(Atom::CodeQuoteArgument, arg); + } +#else + if (!quoting) { + if (priv->text.lastAtom()->type() == Atom::Code + && priv->text.lastAtom()->string().endsWith("\n\n")) + priv->text.lastAtom()->chopString(); + + QString arg = getOptionalArgument(); + int indent = 4; + if (!arg.isEmpty()) + indent = arg.toInt(); + for (int i = 0; i < indent; ++i) + appendToCode(" "); + appendToCode("...\n"); + } + else { + append(Atom::CodeQuoteCommand, cmdStr); + QString arg = getOptionalArgument(); + if (arg.isEmpty()) + arg = "4"; + append(Atom::CodeQuoteArgument, arg); + } +#endif + } + break; + case CMD_ELSE: + if (preprocessorSkipping.size() > 0) { + if (preprocessorSkipping.top()) { + --numPreprocessorSkipping; + } else { + ++numPreprocessorSkipping; + } + preprocessorSkipping.top() = !preprocessorSkipping.top(); + (void)getRestOfLine(); // ### should ensure that it's empty + if (numPreprocessorSkipping) + skipToNextPreprocessorCommand(); + } else { + location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ELSE))); + } + break; + case CMD_ENDABSTRACT: + if (closeCommand(cmd)) { + leavePara(); + append(Atom::AbstractRight); + } + break; + case CMD_ENDCHAPTER: + endSection(0, cmd); + break; + case CMD_ENDCODE: + closeCommand(cmd); + break; +#ifdef QDOC_QML + case CMD_ENDQML: + closeCommand(cmd); + break; +#endif + case CMD_ENDFOOTNOTE: + if (closeCommand(cmd)) { + leavePara(); + append(Atom::FootnoteRight); + paraState = InsideMultiLinePara; // ### + } + break; + case CMD_ENDIF: + if (preprocessorSkipping.count() > 0) { + if (preprocessorSkipping.pop()) + --numPreprocessorSkipping; + (void)getRestOfLine(); // ### should ensure that it's empty + if (numPreprocessorSkipping) + skipToNextPreprocessorCommand(); + } else { + location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ENDIF))); + } + break; + case CMD_ENDLEGALESE: + if (closeCommand(cmd)) { + leavePara(); + append(Atom::LegaleseRight); + } + break; + case CMD_ENDLINK: + if (closeCommand(cmd)) { + if (priv->text.lastAtom()->type() == Atom::String + && priv->text.lastAtom()->string().endsWith(" ")) + priv->text.lastAtom()->chopString(); + append(Atom::FormattingRight, ATOM_FORMATTING_LINK); + } + break; + case CMD_ENDLIST: + if (closeCommand(cmd)) { + leavePara(); + if (openedLists.top().isStarted()) { + append(Atom::ListItemRight, + openedLists.top().styleString()); + append(Atom::ListRight, + openedLists.top().styleString()); + } + openedLists.pop(); + } + break; + case CMD_ENDOMIT: + closeCommand(cmd); + break; + case CMD_ENDPART: + endSection(-1, cmd); + break; + case CMD_ENDQUOTATION: + if (closeCommand(cmd)) { + leavePara(); + append(Atom::QuotationRight); + } + break; + case CMD_ENDRAW: + location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ENDRAW))); + break; + case CMD_ENDSECTION1: + endSection(1, cmd); + break; + case CMD_ENDSECTION2: + endSection(2, cmd); + break; + case CMD_ENDSECTION3: + endSection(3, cmd); + break; + case CMD_ENDSECTION4: + endSection(4, cmd); + break; + case CMD_ENDSIDEBAR: + if (closeCommand(cmd)) { + leavePara(); + append(Atom::SidebarRight); + } + break; + case CMD_ENDTABLE: + if (closeCommand(cmd)) { + leaveTableRow(); + append(Atom::TableRight); + } + break; + case CMD_EXPIRE: + checkExpiry(getArgument()); + break; + case CMD_FOOTNOTE: + if (openCommand(cmd)) { + enterPara(); + append(Atom::FootnoteLeft); + paraState = OutsidePara; // ### + } + break; + case CMD_GENERATELIST: + append(Atom::GeneratedList, getArgument()); + break; + case CMD_GRANULARITY: + priv->constructExtra(); + priv->extra->granularity = getSectioningUnit(); + break; + case CMD_HEADER: + if (openedCommands.top() == CMD_TABLE) { + leaveTableRow(); + append(Atom::TableHeaderLeft); + inTableHeader = true; + } else { + if (openedCommands.contains(CMD_TABLE)) { + location().warning(tr("Cannot use '\\%1' within '\\%2'") + .arg(cmdName(CMD_HEADER)) + .arg(cmdName(openedCommands.top()))); + } else { + location().warning(tr("Cannot use '\\%1' outside of '\\%2'") + .arg(cmdName(CMD_HEADER)) + .arg(cmdName(CMD_TABLE))); + } + } + break; + case CMD_I: + startFormat(ATOM_FORMATTING_ITALIC, cmd); + break; + case CMD_IF: + preprocessorSkipping.push(!Tokenizer::isTrue(getRestOfLine())); + if (preprocessorSkipping.top()) + ++numPreprocessorSkipping; + if (numPreprocessorSkipping) + skipToNextPreprocessorCommand(); + break; + case CMD_IMAGE: + leaveValueList(); + append(Atom::Image, getArgument()); + append(Atom::ImageText, getRestOfLine()); + break; + case CMD_INCLUDE: + include(getArgument()); + break; + case CMD_INLINEIMAGE: + enterPara(); + append(Atom::InlineImage, getArgument()); + append(Atom::ImageText, getRestOfLine()); + append(Atom::String, " "); + break; + case CMD_INDEX: + if (paraState == OutsidePara) { + enterPara(); + indexStartedPara = true; + } else { + const Atom *last = priv->text.lastAtom(); + if (indexStartedPara && + (last->type() != Atom::FormattingRight || + last->string() != ATOM_FORMATTING_INDEX)) + indexStartedPara = false; + } + startFormat(ATOM_FORMATTING_INDEX, cmd); + break; + case CMD_KEYWORD: + insertTarget(getRestOfLine(),true); + break; + case CMD_L: + enterPara(); + if (isLeftBraceAhead()) { + x = getArgument(); + append(Atom::Link, x); + if (isLeftBraceAhead()) { + currentLinkAtom = priv->text.lastAtom(); + startFormat(ATOM_FORMATTING_LINK, cmd); + } else { + append(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + append(Atom::String, cleanLink(x)); + append(Atom::FormattingRight, ATOM_FORMATTING_LINK); + } + } else { + x = getArgument(); + append(Atom::Link, x); + append(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + append(Atom::String, cleanLink(x)); + append(Atom::FormattingRight, ATOM_FORMATTING_LINK); + } + break; + case CMD_LEGALESE: + leavePara(); + if (openCommand(cmd)) + append(Atom::LegaleseLeft); + docPrivate->hasLegalese = true; + break; + case CMD_LINK: + if (openCommand(cmd)) { + enterPara(); + x = getArgument(); + append(Atom::Link, x); + append(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + skipSpacesOrOneEndl(); + } + break; + case CMD_LIST: + if (openCommand(cmd)) { + leavePara(); + openedLists.push(OpenedList(location(), + getOptionalArgument())); + } + break; + case CMD_META: + priv->constructExtra(); + x = getArgument(); + priv->extra->metaMap.insert(x, getRestOfLine()); + break; + case CMD_NEWCODE: + location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_NEWCODE))); + break; + case CMD_O: + leavePara(); + if (openedCommands.top() == CMD_LIST) { + if (openedLists.top().isStarted()) { + append(Atom::ListItemRight, + openedLists.top().styleString()); + } else { + append(Atom::ListLeft, + openedLists.top().styleString()); + } + openedLists.top().next(); + append(Atom::ListItemNumber, + openedLists.top().numberString()); + append(Atom::ListItemLeft, + openedLists.top().styleString()); + enterPara(); + } else if (openedCommands.top() == CMD_TABLE) { + x = "1,1"; + if (isLeftBraceAhead()) + x = getArgument(); + + if (!inTableHeader && !inTableRow) { + location().warning(tr("Missing '\\%1' or '\\%1' before '\\%3'") + .arg(cmdName(CMD_HEADER)) + .arg(cmdName(CMD_ROW)) + .arg(cmdName(CMD_O))); + append(Atom::TableRowLeft); + inTableRow = true; + } else if (inTableItem) { + append(Atom::TableItemRight); + inTableItem = false; + } + + append(Atom::TableItemLeft, x); + inTableItem = true; + } else { + location().warning(tr("Command '\\%1' outside of '\\%2' and '\\%3'") + .arg(cmdName(cmd)) + .arg(cmdName(CMD_LIST)) + .arg(cmdName(CMD_TABLE))); + } + break; + case CMD_OLDCODE: + leavePara(); +#ifdef QDOC2DOX + if (DoxWriter::isDoxPass()) { + append(Atom::CodeOld, getUnmarkedCode(CMD_OLDCODE)); + append(Atom::CodeNew, getUnmarkedCode(CMD_NEWCODE)); + } + else { + append(Atom::CodeOld, getCode(CMD_OLDCODE, marker)); + append(Atom::CodeNew, getCode(CMD_NEWCODE, marker)); + } +#else + append(Atom::CodeOld, getCode(CMD_OLDCODE, marker)); + append(Atom::CodeNew, getCode(CMD_NEWCODE, marker)); +#endif + break; + case CMD_OMIT: + getUntilEnd(cmd); + break; + case CMD_OMITVALUE: + x = getArgument(); + if (!priv->enumItemList.contains(x)) + priv->enumItemList.append(x); + if (!priv->omitEnumItemList.contains(x)) + priv->omitEnumItemList.append(x); + break; + case CMD_PART: + startSection(Doc::Part, cmd); + break; + case CMD_PRINTLINE: + leavePara(); + if (!quoting) + appendToCode(quoter.quoteLine(location(), cmdStr, + getRestOfLine())); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getRestOfLine()); + } + break; + case CMD_PRINTTO: + leavePara(); + if (!quoting) + appendToCode(quoter.quoteTo(location(), cmdStr, + getRestOfLine())); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getRestOfLine()); + } + break; + case CMD_PRINTUNTIL: + leavePara(); + if (!quoting) + appendToCode(quoter.quoteUntil(location(), cmdStr, + getRestOfLine())); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getRestOfLine()); + } + break; + case CMD_QUOTATION: + if (openCommand(cmd)) { + leavePara(); + append(Atom::QuotationLeft); + } + break; + case CMD_QUOTEFILE: + { + leavePara(); + QString fileName = getArgument(); + Doc::quoteFromFile(location(), quoter, fileName); + if (!quoting) { + append(Atom::Code, + quoter.quoteTo(location(), cmdStr, "")); + quoter.reset(); + } else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, fileName); + } + break; + } + case CMD_QUOTEFROMFILE: + leavePara(); + if (!quoting) + quoteFromFile(); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getArgument()); + } + break; + case CMD_QUOTEFUNCTION: + leavePara(); + marker = quoteFromFile(); + x = getRestOfLine(); + if (!quoting) { + quoter.quoteTo(location(), cmdStr, + slashed(marker->functionBeginRegExp(x))); + append(Atom::Code, + quoter.quoteUntil(location(), cmdStr, + slashed(marker->functionEndRegExp(x)))); + quoter.reset(); + } else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, slashed(marker->functionEndRegExp(x))); + } + break; + case CMD_RAW: + leavePara(); + x = getRestOfLine(); + if (x.isEmpty()) + location().warning(tr("Missing format name after '\\%1") + .arg(cmdName(CMD_RAW))); + append(Atom::FormatIf, x); + append(Atom::RawString, untabifyEtc(getUntilEnd(cmd))); + append(Atom::FormatElse); + append(Atom::FormatEndif); + break; + case CMD_ROW: + if (openedCommands.top() == CMD_TABLE) { + leaveTableRow(); + append(Atom::TableRowLeft); + inTableRow = true; + } else { + if (openedCommands.contains(CMD_TABLE)) { + location().warning(tr("Cannot use '\\%1' within '\\%2'") + .arg(cmdName(CMD_ROW)) + .arg(cmdName(openedCommands.top()))); + } else { + location().warning(tr("Cannot use '\\%1' outside of '\\%2'") + .arg(cmdName(CMD_ROW)) + .arg(cmdName(CMD_TABLE))); + } + } + break; + case CMD_SA: + parseAlso(); + break; + case CMD_SECTION1: + startSection(Doc::Section1, cmd); + break; + case CMD_SECTION2: + startSection(Doc::Section2, cmd); + break; + case CMD_SECTION3: + startSection(Doc::Section3, cmd); + break; + case CMD_SECTION4: + startSection(Doc::Section4, cmd); + break; + case CMD_SIDEBAR: + if (openCommand(cmd)) { + leavePara(); + append(Atom::SidebarLeft); + } + break; + case CMD_SKIPLINE: + leavePara(); + if (!quoting) + quoter.quoteLine(location(), + cmdStr, + getRestOfLine()); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getRestOfLine()); + } + break; + case CMD_SKIPTO: + leavePara(); + if (!quoting) + quoter.quoteTo(location(), + cmdStr, + getRestOfLine()); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getRestOfLine()); + } + break; + case CMD_SKIPUNTIL: + leavePara(); + if (!quoting) + quoter.quoteUntil(location(), + cmdStr, + getRestOfLine()); + else { + append(Atom::CodeQuoteCommand, cmdStr); + append(Atom::CodeQuoteArgument, getRestOfLine()); + } + break; + case CMD_SNIPPET: + leavePara(); + { + QString snippet = getArgument(); + QString identifier = getRestOfLine(); +#ifdef QDOC2DOX + if (quoting || DoxWriter::isDoxPass()) { + append(Atom::SnippetCommand, cmdStr); + append(Atom::SnippetLocation, snippet); + append(Atom::SnippetIdentifier, identifier); + } else { + Doc::quoteFromFile(location(),quoter,snippet); + appendToCode(quoter.quoteSnippet(location(), + identifier)); + } +#else + if (quoting) { + append(Atom::SnippetCommand, cmdStr); + append(Atom::SnippetLocation, snippet); + append(Atom::SnippetIdentifier, identifier); + } else { + Doc::quoteFromFile(location(),quoter,snippet); + appendToCode(quoter.quoteSnippet(location(), + identifier)); + } +#endif + } + break; + case CMD_SUB: + startFormat(ATOM_FORMATTING_SUBSCRIPT, cmd); + break; + case CMD_SUP: + startFormat(ATOM_FORMATTING_SUPERSCRIPT, cmd); + break; + case CMD_TABLE: + x = getRestOfLine(); + if (openCommand(cmd)) { + leavePara(); + append(Atom::TableLeft, x); + inTableHeader = false; + inTableRow = false; + inTableItem = false; + } + break; + case CMD_TABLEOFCONTENTS: + x = "1"; + if (isLeftBraceAhead()) + x = getArgument(); + x += ","; + x += QString::number((int)getSectioningUnit()); + append(Atom::TableOfContents, x); + break; + case CMD_TARGET: + insertTarget(getRestOfLine(),false); + break; + case CMD_TT: + startFormat(ATOM_FORMATTING_TELETYPE, cmd); + break; + case CMD_UNDERLINE: + startFormat(ATOM_FORMATTING_UNDERLINE, cmd); + break; + case CMD_UNICODE: + enterPara(); + x = getArgument(); + { + bool ok; + uint unicodeChar = x.toUInt(&ok, 0); + if (!ok || + (unicodeChar == 0x0000) || + (unicodeChar > 0xFFFE)) { + location().warning(tr("Invalid Unicode character '%1' specified " + "with '%2'") + .arg(x, cmdName(CMD_UNICODE))); + } else { + append(Atom::String, QChar(unicodeChar)); + } + } + break; + case CMD_VALUE: + leaveValue(); + if (openedLists.top().style() == OpenedList::Value) { + x = getArgument(); + if (!priv->enumItemList.contains(x)) + priv->enumItemList.append(x); + + openedLists.top().next(); + append(Atom::ListTagLeft, ATOM_LIST_VALUE); + append(Atom::String, x); + append(Atom::ListTagRight, ATOM_LIST_VALUE); + append(Atom::ListItemLeft, ATOM_LIST_VALUE); + + skipSpacesOrOneEndl(); + if (isBlankLine()) + append(Atom::Nop); + } else { + // ### problems + } + break; + case CMD_WARNING: + startNewPara(); + append(Atom::FormattingLeft, ATOM_FORMATTING_BOLD); + append(Atom::String, "Warning:"); + append(Atom::FormattingRight, ATOM_FORMATTING_BOLD); + append(Atom::String, " "); + break; + case CMD_OVERLOAD: // qdoc --> doxygen + priv->metacommandsUsed.insert(cmdStr); + x.clear(); + if (!isBlankLine()) + x = getRestOfLine(); + if (!x.isEmpty()) { + append(Atom::ParaLeft); + append(Atom::String, "This function overloads "); + append(Atom::AutoLink,x); + append(Atom::String, "."); + append(Atom::ParaRight); + } + else { + append(Atom::ParaLeft); + append(Atom::String, + "This is an overloaded member function, " + "provided for convenience."); + append(Atom::ParaRight); + x = getMetaCommandArgument(cmdStr); + } + priv->metaCommandMap[cmdStr].append(x); + break; + case NOT_A_CMD: + if (metaCommandSet.contains(cmdStr)) { + priv->metacommandsUsed.insert(cmdStr); + QString xxx = getMetaCommandArgument(cmdStr); + priv->metaCommandMap[cmdStr].append(xxx); + } + else if (macroHash()->contains(cmdStr)) { + const Macro ¯o = macroHash()->value(cmdStr); + int numPendingFi = 0; + QStringMap::ConstIterator d; + d = macro.otherDefs.begin(); + while (d != macro.otherDefs.end()) { + append(Atom::FormatIf, d.key()); + expandMacro(cmdStr, *d, macro.numParams); + ++d; + + if (d == macro.otherDefs.end()) { + append(Atom::FormatEndif); + } + else { + append(Atom::FormatElse); + numPendingFi++; + } + } + while (numPendingFi-- > 0) + append(Atom::FormatEndif); + + if (!macro.defaultDef.isEmpty()) { + if (!macro.otherDefs.isEmpty()) { + macro.defaultDefLocation.warning( + tr("Macro cannot have both " + "format-specific and qdoc- " + "syntax definitions")); + } + else { + location().push(macro.defaultDefLocation.filePath()); + in.insert(pos, macro.defaultDef); + len = in.length(); + openedInputs.push(pos + macro.defaultDef.length()); + } + } + } + else { + location().warning( + tr("Unknown command '\\%1'").arg(cmdStr), + detailsUnknownCommand(metaCommandSet,cmdStr)); + enterPara(); + append(Atom::UnknownCommand, cmdStr); + } + } + } + } + break; + case '{': + enterPara(); + appendChar('{'); + braceDepth++; + pos++; + break; + case '}': + { + braceDepth--; + pos++; + + QMap<int, QString>::Iterator f = + pendingFormats.find(braceDepth); + if (f == pendingFormats.end()) { + enterPara(); + appendChar('}'); + } + else { + append(Atom::FormattingRight, *f); + if (*f == ATOM_FORMATTING_INDEX) { + if (indexStartedPara) + skipAllSpaces(); + } + else if (*f == ATOM_FORMATTING_LINK) { + // hack for C++ to support links like + // \l{QString::}{count()} + if (currentLinkAtom && + currentLinkAtom->string().endsWith("::")) { + QString suffix = Text::subText(currentLinkAtom, + priv->text.lastAtom()).toString(); + currentLinkAtom->appendString(suffix); + } + currentLinkAtom = 0; + } + pendingFormats.erase(f); + } + } + break; + default: + { + bool newWord; + switch (priv->text.lastAtom()->type()) { + case Atom::ParaLeft: + newWord = true; + break; + default: + newWord = false; + } + + if (paraState == OutsidePara) { + if (ch.isSpace()) { + ++pos; + newWord = false; + } else { + enterPara(); + newWord = true; + } + } + else { + if (ch.isSpace()) { + ++pos; + if ((ch == '\n') && + (paraState == InsideSingleLinePara || + isBlankLine())) { + leavePara(); + newWord = false; + } + else { + appendChar(' '); + newWord = true; + } + } + else { + newWord = true; + } + } + + if (newWord) { + int startPos = pos; + int numInternalUppercase = 0; + int numLowercase = 0; + int numStrangeSymbols = 0; + + while (pos < len) { + unsigned char latin1Ch = in.at(pos).toLatin1(); + if (islower(latin1Ch)) { + ++numLowercase; + ++pos; + } + else if (isupper(latin1Ch)) { + if (pos > startPos) + ++numInternalUppercase; + ++pos; + } + else if (isdigit(latin1Ch)) { + if (pos > startPos) { + ++pos; + } + else { + break; + } + } + else if (latin1Ch == '_' || latin1Ch == '@') { + ++numStrangeSymbols; + ++pos; + } + else if (latin1Ch == ':' && pos < len - 1 + && in.at(pos + 1) == QLatin1Char(':')) { + ++numStrangeSymbols; + pos += 2; + } + else if (latin1Ch == '(') { + if (pos > startPos) { + if (pos < len - 1 && + in.at(pos + 1) == QLatin1Char(')')) { + ++numStrangeSymbols; + pos += 2; + break; + } + else { + // ### handle functions with signatures + // and function calls + break; + } + } + else { + break; + } + } + else { + break; + } + } + + if (pos == startPos) { + if (!ch.isSpace()) { + appendChar(ch); + ++pos; + } + } + else { + QString word = in.mid(startPos, pos - startPos); + // is word a C++ symbol or an English word? + if ((numInternalUppercase >= 1 && numLowercase >= 2) + || numStrangeSymbols >= 1) { + append(Atom::AutoLink, word); + } + else { + appendWord(word); + } + } + } + } + } + } + leaveValueList(); + + // for compatibility + if (openedCommands.top() == CMD_LEGALESE) { + append(Atom::LegaleseRight); + openedCommands.pop(); + } + + if (openedCommands.top() != CMD_OMIT) { + location().warning(tr("Missing '\\%1'").arg(endCmdName(openedCommands.top()))); + } else if (preprocessorSkipping.count() > 0) { + location().warning(tr("Missing '\\%1'").arg(cmdName(CMD_ENDIF))); + } + + while (currentSectioningUnit > Doc::Chapter) { + int delta = currentSectioningUnit - priv->extra->sectioningUnit; + append(Atom::SectionRight, QString::number(delta)); + currentSectioningUnit = Doc::SectioningUnit(int(currentSectioningUnit) - 1); + } + + if (priv->extra && priv->extra->granularity < priv->extra->sectioningUnit) + priv->extra->granularity = priv->extra->sectioningUnit; + priv->text.stripFirstAtom(); +} + +Location &DocParser::location() +{ + while (!openedInputs.isEmpty() && openedInputs.top() <= pos) { + cachedLoc.pop(); + cachedPos = openedInputs.pop(); + } + while (cachedPos < pos) + cachedLoc.advance(in.at(cachedPos++)); + return cachedLoc; +} + +QString DocParser::detailsUnknownCommand(const QSet<QString> &metaCommandSet, + const QString &str) +{ + QSet<QString> commandSet = metaCommandSet; + int i = 0; + while (cmds[i].english != 0) { + commandSet.insert(*cmds[i].alias); + i++; + } + + if (aliasMap()->contains(str)) + return tr("The command '\\%1' was renamed '\\%2' by the configuration" + " file. Use the new name.") + .arg(str).arg((*aliasMap())[str]); + + QString best = nearestName(str, commandSet); + if (best.isEmpty()) + return QString(); + return tr("Maybe you meant '\\%1'?").arg(best); +} + +void DocParser::checkExpiry(const QString& date) +{ + QRegExp ymd("(\\d{4})(?:-(\\d{2})(?:-(\\d{2})))"); + + if (ymd.exactMatch(date)) { + int y = ymd.cap(1).toInt(); + int m = ymd.cap(2).toInt(); + int d = ymd.cap(3).toInt(); + + if (m == 0) + m = 1; + if (d == 0) + d = 1; + QDate expiryDate(y, m, d); + if (expiryDate.isValid()) { + int days = expiryDate.daysTo(QDate::currentDate()); + if (days == 0) { + location().warning(tr("Documentation expires today")); + } else if (days == 1) { + location().warning(tr("Documentation expired yesterday")); + } else if (days >= 2) { + location().warning(tr("Documentation expired %1 days ago") + .arg(days)); + } + } else { + location().warning(tr("Date '%1' invalid").arg(date)); + } + } else { + location().warning(tr("Date '%1' not in YYYY-MM-DD format") + .arg(date)); + } +} + +void DocParser::insertBaseName(const QString &baseName) +{ + priv->constructExtra(); + if (currentSectioningUnit == priv->extra->sectioningUnit) { + priv->extra->baseName = baseName; + } else { + Atom *atom = priv->text.firstAtom(); + Atom *sectionLeft = 0; + + int delta = currentSectioningUnit - priv->extra->sectioningUnit; + + while (atom != 0) { + if (atom->type() == Atom::SectionLeft && + atom->string().toInt() == delta) + sectionLeft = atom; + atom = atom->next(); + } + if (sectionLeft != 0) + (void) new Atom(sectionLeft, Atom::BaseName, baseName); + } +} + +void DocParser::insertTarget(const QString &target, bool keyword) +{ + if (targetMap.contains(target)) { + location().warning(tr("Duplicate target name '%1'").arg(target)); + targetMap[target].warning(tr("(The previous occurrence is here)")); + } else { + targetMap.insert(target, location()); + append(Atom::Target, target); + priv->constructExtra(); + if (keyword) + priv->extra->keywords.append(priv->text.lastAtom()); + else + priv->extra->targets.append(priv->text.lastAtom()); + } +} + +void DocParser::include(const QString& fileName) +{ + if (location().depth() > 16) + location().fatal(tr("Too many nested '\\%1's") + .arg(cmdName(CMD_INCLUDE))); + + QString userFriendlyFilePath; + // ### use current directory? + QString filePath = Config::findFile(location(), + sourceFiles, + sourceDirs, + fileName, + userFriendlyFilePath); + if (filePath.isEmpty()) { + location().warning(tr("Cannot find leaf file '%1'").arg(fileName)); + } else { + QFile inFile(filePath); + if (!inFile.open(QFile::ReadOnly)) { + location().warning(tr("Cannot open leaf file '%1'") + .arg(userFriendlyFilePath)); + } else { + location().push(userFriendlyFilePath); + + QTextStream inStream(&inFile); + QString includedStuff = inStream.readAll(); + inFile.close(); + + in.insert(pos, includedStuff); + len = in.length(); + openedInputs.push(pos + includedStuff.length()); + } + } +} + +void DocParser::startFormat(const QString& format, int cmd) +{ + enterPara(); + + QMap<int, QString>::ConstIterator f = pendingFormats.begin(); + while (f != pendingFormats.end()) { + if (*f == format) { + location().warning(tr("Cannot nest '\\%1' commands") + .arg(cmdName(cmd))); + return; + } + ++f; + } + + append(Atom::FormattingLeft, format); + + if (isLeftBraceAhead()) { + skipSpacesOrOneEndl(); + pendingFormats.insert(braceDepth, format); + ++braceDepth; + ++pos; + } else { + append(Atom::String, getArgument()); + append(Atom::FormattingRight, format); + if (format == ATOM_FORMATTING_INDEX && indexStartedPara) { + skipAllSpaces(); + indexStartedPara = false; + } + } +} + +bool DocParser::openCommand(int cmd) +{ + int outer = openedCommands.top(); + bool ok = true; + + if (cmd != CMD_LINK) { + if (outer == CMD_LIST) { + ok = (cmd == CMD_FOOTNOTE || cmd == CMD_LIST); + } else if (outer == CMD_ABSTRACT) { + ok = (cmd == CMD_LIST || + cmd == CMD_QUOTATION || + cmd == CMD_TABLE); + } else if (outer == CMD_SIDEBAR) { + ok = (cmd == CMD_LIST || + cmd == CMD_QUOTATION || + cmd == CMD_SIDEBAR); + } else if (outer == CMD_QUOTATION) { + ok = (cmd == CMD_LIST); + } else if (outer == CMD_TABLE) { + ok = (cmd == CMD_LIST || + cmd == CMD_FOOTNOTE || + cmd == CMD_QUOTATION); + } else if (outer == CMD_FOOTNOTE || outer == CMD_LINK) { + ok = false; + } + } + + if (ok) { + openedCommands.push(cmd); + } else { + location().warning(tr("Cannot use '\\%1' within '\\%2'") + .arg(cmdName(cmd)).arg(cmdName(outer))); + } + return ok; +} + +bool DocParser::closeCommand(int endCmd) +{ + if (endCmdFor(openedCommands.top()) == endCmd && openedCommands.size() > 1) { + openedCommands.pop(); + return true; + } else { + bool contains = false; + QStack<int> opened2 = openedCommands; + while (opened2.size() > 1) { + if (endCmdFor(opened2.top()) == endCmd) { + contains = true; + break; + } + opened2.pop(); + } + + if (contains) { + while (endCmdFor(openedCommands.top()) != endCmd && openedCommands.size() > 1) { + location().warning(tr("Missing '\\%1' before '\\%2'") + .arg(endCmdName(openedCommands.top())) + .arg(cmdName(endCmd))); + openedCommands.pop(); + } + } else { + location().warning(tr("Unexpected '\\%1'") + .arg(cmdName(endCmd))); + } + return false; + } +} + +void DocParser::startSection(Doc::SectioningUnit unit, int cmd) +{ + leavePara(); + + if (currentSectioningUnit == Doc::Book) { + if (unit > Doc::Section1) + location().warning(tr("Unexpected '\\%1' without '\\%2'") + .arg(cmdName(cmd)) + .arg(cmdName(CMD_SECTION1))); + currentSectioningUnit = (Doc::SectioningUnit) (unit - 1); + priv->constructExtra(); + priv->extra->sectioningUnit = currentSectioningUnit; + } + + if (unit <= priv->extra->sectioningUnit) { + location().warning(tr("Unexpected '\\%1' in this documentation") + .arg(cmdName(cmd))); + } else if (unit - currentSectioningUnit > 1) { + location().warning(tr("Unexpected '\\%1' at this point") + .arg(cmdName(cmd))); + } else { + if (currentSectioningUnit >= unit) + endSection(unit, cmd); + + int delta = unit - priv->extra->sectioningUnit; + append(Atom::SectionLeft, QString::number(delta)); + priv->constructExtra(); + priv->extra->tableOfContents.append(priv->text.lastAtom()); + priv->extra->tableOfContentsLevels.append(unit); + enterPara(Atom::SectionHeadingLeft, + Atom::SectionHeadingRight, + QString::number(delta)); + currentSectioningUnit = unit; + } +} + +void DocParser::endSection(int unit, int endCmd) +{ + leavePara(); + + if (unit < priv->extra->sectioningUnit) { + location().warning(tr("Unexpected '\\%1' in this documentation") + .arg(cmdName(endCmd))); + } else if (unit > currentSectioningUnit) { + location().warning(tr("Unexpected '\\%1' at this point") + .arg(cmdName(endCmd))); + } else { + while (currentSectioningUnit >= unit) { + int delta = currentSectioningUnit - priv->extra->sectioningUnit; + append(Atom::SectionRight, QString::number(delta)); + currentSectioningUnit = + (Doc::SectioningUnit) (currentSectioningUnit - 1); + } + } +} + +void DocParser::parseAlso() +{ + leavePara(); + skipSpacesOnLine(); + while (pos < len && in[pos] != '\n') { + QString target; + QString str; + + if (in[pos] == '{') { + target = getArgument(); + skipSpacesOnLine(); + if (in[pos] == '{') { + str = getArgument(); + + // hack for C++ to support links like \l{QString::}{count()} + if (target.endsWith("::")) + target += str; + } + else { + str = target; + } +#ifdef QDOC2_COMPAT + } + else if (in[pos] == '\\' && in.mid(pos, 5) == "\\link") { + pos += 6; + target = getArgument(); + int endPos = in.indexOf("\\endlink", pos); + if (endPos != -1) { + str = in.mid(pos, endPos - pos).trimmed(); + pos = endPos + 8; + } +#endif + } + else { + target = getArgument(); + str = cleanLink(target); + } + + Text also; + also << Atom(Atom::Link, target) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << str + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + priv->addAlso(also); + + skipSpacesOnLine(); + if (pos < len && in[pos] == ',') { + pos++; + skipSpacesOrOneEndl(); + } + else if (in[pos] != '\n') { + location().warning(tr("Missing comma in '\\%1'").arg(cmdName(CMD_SA))); + } + } +} + +void DocParser::append(Atom::Type type, const QString &string) +{ + Atom::Type lastType = priv->text.lastAtom()->type(); +#ifdef QDOC_QML + if (((lastType == Atom::Code) || (lastType == Atom::Code)) && +#else + if ((lastType == Atom::Code) && +#endif + priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n"))) + priv->text.lastAtom()->chopString(); + priv->text << Atom(type, string); +} + +void DocParser::appendChar(QChar ch) +{ + if (priv->text.lastAtom()->type() != Atom::String) + append(Atom::String); + Atom *atom = priv->text.lastAtom(); + if (ch == QLatin1Char(' ')) { + if (!atom->string().endsWith(QLatin1Char(' '))) + atom->appendChar(QLatin1Char(' ')); + } + else + atom->appendChar(ch); +} + +void DocParser::appendWord(const QString &word) +{ + if (priv->text.lastAtom()->type() != Atom::String) { + append(Atom::String, word); + } + else + priv->text.lastAtom()->appendString(word); +} + +void DocParser::appendToCode(const QString& markedCode) +{ + Atom::Type lastType = priv->text.lastAtom()->type(); +#ifdef QDOC_QML + if (lastType != Atom::Qml) + append(Atom::Qml); +#else + if (lastType != Atom::Code) + append(Atom::Code); +#endif + priv->text.lastAtom()->appendString(markedCode); +} + +void DocParser::startNewPara() +{ + leavePara(); + enterPara(); +} + +void DocParser::enterPara(Atom::Type leftType, + Atom::Type rightType, + const QString& string) +{ + if (paraState == OutsidePara) { + if (priv->text.lastAtom()->type() != Atom::ListItemLeft) + leaveValueList(); + append(leftType, string); + indexStartedPara = false; + pendingParaLeftType = leftType; + pendingParaRightType = rightType; + pendingParaString = string; + if ( +#if 0 + leftType == Atom::BriefLeft || +#endif + leftType == Atom::SectionHeadingLeft) { + paraState = InsideSingleLinePara; + } else { + paraState = InsideMultiLinePara; + } + skipSpacesOrOneEndl(); + } +} + +void DocParser::leavePara() +{ + if (paraState != OutsidePara) { + if (!pendingFormats.isEmpty()) { + location().warning(tr("Missing '}'")); + pendingFormats.clear(); + } + + if (priv->text.lastAtom()->type() == pendingParaLeftType) { + priv->text.stripLastAtom(); + } else { + if (priv->text.lastAtom()->type() == Atom::String && + priv->text.lastAtom()->string().endsWith(" ")) { + priv->text.lastAtom()->chopString(); + } + append(pendingParaRightType, pendingParaString); + } + paraState = OutsidePara; + indexStartedPara = false; + pendingParaRightType = Atom::Nop; + pendingParaString = ""; + } +} + +void DocParser::leaveValue() +{ + leavePara(); + if (openedLists.isEmpty()) { + openedLists.push(OpenedList(OpenedList::Value)); + append(Atom::ListLeft, ATOM_LIST_VALUE); + } else { + if (priv->text.lastAtom()->type() == Atom::Nop) + priv->text.stripLastAtom(); + append(Atom::ListItemRight, ATOM_LIST_VALUE); + } +} + +void DocParser::leaveValueList() +{ + leavePara(); + if (!openedLists.isEmpty() && + (openedLists.top().style() == OpenedList::Value)) { + if (priv->text.lastAtom()->type() == Atom::Nop) + priv->text.stripLastAtom(); + append(Atom::ListItemRight, ATOM_LIST_VALUE); + append(Atom::ListRight, ATOM_LIST_VALUE); + openedLists.pop(); + } +} + +void DocParser::leaveTableRow() +{ + if (inTableItem) { + leavePara(); + append(Atom::TableItemRight); + inTableItem = false; + } + if (inTableHeader) { + append(Atom::TableHeaderRight); + inTableHeader = false; + } + if (inTableRow) { + append(Atom::TableRowRight); + inTableRow = false; + } +} + +CodeMarker *DocParser::quoteFromFile() +{ + return Doc::quoteFromFile(location(), quoter, getArgument()); +} + +void DocParser::expandMacro(const QString &name, + const QString &def, + int numParams) +{ + if (numParams == 0) { + append(Atom::RawString, def); + } else { + QStringList args; + QString rawString; + + for (int i = 0; i < numParams; i++) { + if (numParams == 1 || isLeftBraceAhead()) { + args << getArgument(true); + } else { + location().warning(tr("Macro '\\%1' invoked with too few" + " arguments (expected %2, got %3)") + .arg(name).arg(numParams).arg(i)); + break; + } + } + + int j = 0; + while (j < def.size()) { + int paramNo; + if ((def[j] == '\\') && (j < def.size() - 1) && + ((paramNo = def[j + 1].digitValue()) >= 1) && + (paramNo <= numParams)) { + if (!rawString.isEmpty()) { + append(Atom::RawString, rawString); + rawString = ""; + } + append(Atom::String, args[paramNo - 1]); + j += 2; + } else { + rawString += def[j++]; + } + } + if (!rawString.isEmpty()) + append(Atom::RawString, rawString); + } +} + +Doc::SectioningUnit DocParser::getSectioningUnit() +{ + QString name = getOptionalArgument(); + + if (name == "part") { + return Doc::Part; + } else if (name == "chapter") { + return Doc::Chapter; + } else if (name == "section1") { + return Doc::Section1; + } else if (name == "section2") { + return Doc::Section2; + } else if (name == "section3") { + return Doc::Section3; + } else if (name == "section4") { + return Doc::Section4; + } else if (name.isEmpty()) { + return Doc::Section4; + } else { + location().warning(tr("Invalid sectioning unit '%1'").arg(name)); + return Doc::Book; + } +} + +QString DocParser::getArgument(bool verbatim) +{ + QString arg; + int delimDepth = 0; + + skipSpacesOrOneEndl(); + + int startPos = pos; + + /* + Typically, an argument ends at the next white-space. However, + braces can be used to group words: + + {a few words} + + Also, opening and closing parentheses have to match. Thus, + + printf("%d\n", x) + + is an argument too, although it contains spaces. Finally, + trailing punctuation is not included in an argument, nor is 's. + */ + if (pos < (int) in.length() && in[pos] == '{') { + pos++; + while (pos < (int) in.length() && delimDepth >= 0) { + switch (in[pos].unicode()) { + case '{': + delimDepth++; + arg += "{"; + pos++; + break; + case '}': + delimDepth--; + if (delimDepth >= 0) + arg += "}"; + pos++; + break; + case '\\': + if (verbatim) { + arg += in[pos]; + pos++; + } else { + pos++; + if (pos < (int) in.length()) { + if (in[pos].isLetterOrNumber()) + break; + arg += in[pos]; + if (in[pos].isSpace()) { + skipAllSpaces(); + } else { + pos++; + } + } + } + break; + default: + arg += in[pos]; + pos++; + } + } + if (delimDepth > 0) + location().warning(tr("Missing '}'")); + } else { + while (pos < in.length() && + ((delimDepth > 0) || + ((delimDepth == 0) && + !in[pos].isSpace()))) { + switch (in[pos].unicode()) { + case '(': + case '[': + case '{': + delimDepth++; + arg += in[pos]; + pos++; + break; + case ')': + case ']': + case '}': + delimDepth--; + if (pos == startPos || delimDepth >= 0) { + arg += in[pos]; + pos++; + } + break; + case '\\': + if (verbatim) { + arg += in[pos]; + pos++; + } else { + pos++; + if (pos < (int) in.length()) { + if (in[pos].isLetterOrNumber()) + break; + arg += in[pos]; + if (in[pos].isSpace()) { + skipAllSpaces(); + } else { + pos++; + } + } + } + break; + default: + arg += in[pos]; + pos++; + } + } + if ((arg.length() > 1) && + (QString(".,:;!?").indexOf(in[pos - 1]) != -1) && + !arg.endsWith("...")) { + arg.truncate(arg.length() - 1); + pos--; + } + if (arg.length() > 2 && in.mid(pos - 2, 2) == "'s") { + arg.truncate(arg.length() - 2); + pos -= 2; + } + } + return arg.simplified(); +} + +QString DocParser::getOptionalArgument() +{ + skipSpacesOrOneEndl(); + if (pos + 1 < (int) in.length() && in[pos] == '\\' && + in[pos + 1].isLetterOrNumber()) { + return ""; + } else { + return getArgument(); + } +} + +QString DocParser::getRestOfLine() +{ + QString t; + + skipSpacesOnLine(); + + bool trailingSlash = false; + + do { + int begin = pos; + + while (pos < in.size() && in[pos] != '\n') { + if (in[pos] == '\\' && !trailingSlash) { + trailingSlash = true; + ++pos; + while ((pos < in.size()) && + in[pos].isSpace() && + (in[pos] != '\n')) + ++pos; + } else { + trailingSlash = false; + ++pos; + } + } + + if (!t.isEmpty()) + t += " "; + t += in.mid(begin, pos - begin).simplified(); + + if (trailingSlash) { + t.chop(1); + t = t.simplified(); + } + if (pos < in.size()) + ++pos; + } while (pos < in.size() && trailingSlash); + + return t; +} + +/*! + The metacommand argument is normally the remaining text to + the right of the metacommand itself. The extra blanks are + stripped and the argument string is returned. + */ +QString DocParser::getMetaCommandArgument(const QString &cmdStr) +{ + skipSpacesOnLine(); + + int begin = pos; + int parenDepth = 0; + + while (pos < in.size() && (in[pos] != '\n' || parenDepth > 0)) { + if (in.at(pos) == '(') + ++parenDepth; + else if (in.at(pos) == ')') + --parenDepth; + + ++pos; + } + if (pos == in.size() && parenDepth > 0) { + pos = begin; + location().warning(tr("Unbalanced parentheses in '%1'").arg(cmdStr)); + } + + QString t = in.mid(begin, pos - begin).simplified(); + skipSpacesOnLine(); + return t; +} + +QString DocParser::getUntilEnd(int cmd) +{ + int endCmd = endCmdFor(cmd); + QRegExp rx("\\\\" + cmdName(endCmd) + "\\b"); + QString t; + int end = rx.indexIn(in, pos); + + if (end == -1) { + location().warning(tr("Missing '\\%1'").arg(cmdName(endCmd))); + pos = in.length(); + } else { + t = in.mid(pos, end - pos); + pos = end + rx.matchedLength(); + } + return t; +} + +QString DocParser::getCode(int cmd, CodeMarker *marker) +{ + QString code = untabifyEtc(getUntilEnd(cmd)); + int indent = indentLevel(code); + if (indent < minIndent) + minIndent = indent; + code = unindent(minIndent, code); + marker = CodeMarker::markerForCode(code); + return marker->markedUpCode(code, 0, ""); +} + +/*! + Used only for generating doxygen output. + */ +QString DocParser::getUnmarkedCode(int cmd) +{ + QString code = getUntilEnd(cmd); +#if 0 + int indent = indentLevel(code); + if (indent < minIndent) + minIndent = indent; + code = unindent(minIndent, code); +#endif + return code; +} + +bool DocParser::isBlankLine() +{ + int i = pos; + + while (i < len && in[i].isSpace()) { + if (in[i] == '\n') + return true; + i++; + } + return false; +} + +bool DocParser::isLeftBraceAhead() +{ + int numEndl = 0; + int i = pos; + + while (i < len && in[i].isSpace() && numEndl < 2) { + // ### bug with '\\' + if (in[i] == '\n') + numEndl++; + i++; + } + return numEndl < 2 && i < len && in[i] == '{'; +} + +void DocParser::skipSpacesOnLine() +{ + while ((pos < in.length()) && + in[pos].isSpace() && + (in[pos].unicode() != '\n')) + ++pos; +} + +void DocParser::skipSpacesOrOneEndl() +{ + int firstEndl = -1; + while (pos < (int) in.length() && in[pos].isSpace()) { + QChar ch = in[pos]; + if (ch == '\n') { + if (firstEndl == -1) { + firstEndl = pos; + } else { + pos = firstEndl; + break; + } + } + pos++; + } +} + +void DocParser::skipAllSpaces() +{ + while (pos < len && in[pos].isSpace()) + pos++; +} + +void DocParser::skipToNextPreprocessorCommand() +{ + QRegExp rx("\\\\(?:" + cmdName(CMD_IF) + "|" + + cmdName(CMD_ELSE) + "|" + + cmdName(CMD_ENDIF) + ")\\b"); + int end = rx.indexIn(in, pos + 1); // ### + 1 necessary? + + if (end == -1) + pos = in.length(); + else + pos = end; +} + +int DocParser::endCmdFor(int cmd) +{ + switch (cmd) { + case CMD_ABSTRACT: + return CMD_ENDABSTRACT; + case CMD_BADCODE: + return CMD_ENDCODE; + case CMD_CHAPTER: + return CMD_ENDCHAPTER; + case CMD_CODE: + return CMD_ENDCODE; +#ifdef QDOC_QML + case CMD_QML: + return CMD_ENDQML; +#endif + case CMD_FOOTNOTE: + return CMD_ENDFOOTNOTE; + case CMD_LEGALESE: + return CMD_ENDLEGALESE; + case CMD_LINK: + return CMD_ENDLINK; + case CMD_LIST: + return CMD_ENDLIST; + case CMD_NEWCODE: + return CMD_ENDCODE; + case CMD_OLDCODE: + return CMD_NEWCODE; + case CMD_OMIT: + return CMD_ENDOMIT; + case CMD_PART: + return CMD_ENDPART; + case CMD_QUOTATION: + return CMD_ENDQUOTATION; + case CMD_RAW: + return CMD_ENDRAW; + case CMD_SECTION1: + return CMD_ENDSECTION1; + case CMD_SECTION2: + return CMD_ENDSECTION2; + case CMD_SECTION3: + return CMD_ENDSECTION3; + case CMD_SECTION4: + return CMD_ENDSECTION4; + case CMD_SIDEBAR: + return CMD_ENDSIDEBAR; + case CMD_TABLE: + return CMD_ENDTABLE; + default: + return cmd; + } +} + +QString DocParser::cmdName(int cmd) +{ + return *cmds[cmd].alias; +} + +QString DocParser::endCmdName(int cmd) +{ + return cmdName(endCmdFor(cmd)); +} + +QString DocParser::untabifyEtc(const QString& str) +{ + QString result; + result.reserve(str.length()); + int column = 0; + + for (int i = 0; i < str.length(); i++) { + const QChar c = str.at(i); + if (c == QLatin1Char('\r')) + continue; + if (c == QLatin1Char('\t')) { + result += " " + (column % tabSize); + column = ((column / tabSize) + 1) * tabSize; + continue; + } + if (c == QLatin1Char('\n')) { + while (result.endsWith(QLatin1Char(' '))) + result.chop(1); + result += c; + column = 0; + continue; + } + result += c; + column++; + } + + while (result.endsWith("\n\n")) + result.truncate(result.length() - 1); + while (result.startsWith("\n")) + result = result.mid(1); + + return result; +} + +int DocParser::indentLevel(const QString& str) +{ + int minIndent = INT_MAX; + int column = 0; + + for (int i = 0; i < (int) str.length(); i++) { + if (str[i] == '\n') { + column = 0; + } else { + if (str[i] != ' ' && column < minIndent) + minIndent = column; + column++; + } + } + return minIndent; +} + +QString DocParser::unindent(int level, const QString& str) +{ + if (level == 0) + return str; + + QString t; + int column = 0; + + for (int i = 0; i < (int) str.length(); i++) { + if (str[i] == QLatin1Char('\n')) { + t += '\n'; + column = 0; + } else { + if (column >= level) + t += str[i]; + column++; + } + } + return t; +} + +QString DocParser::slashed(const QString& str) +{ + QString result = str; + result.replace("/", "\\/"); + return "/" + result + "/"; +} + +#define COMMAND_BRIEF Doc::alias("brief") + +#ifdef QDOC_QML +#define COMMAND_QMLBRIEF Doc::alias("qmlbrief") +#endif + +#ifdef QDOC2DOX +#define DOXYGEN_INDENT 2 +#define DOXYGEN_TAB_SIZE 4 +#define DOXYGEN_INDENT_STRING " " +#define DOXYGEN_TAB_STRING " " + +static QRegExp ws_rx("\\s"); +static QRegExp not_ws_rx("\\S"); + +int DoxWriter::doxPass = 0; +QString DoxWriter::currentClass; +QSet<QString> DoxWriter::anchors; +QStringMap DoxWriter::exampleTitles; +QStringMap DoxWriter::headerFileTitles; +QStringMap DoxWriter::fileTitles; +QStringMap DoxWriter::groupTitles; +QStringMap DoxWriter::moduleTitles; +QStringMap DoxWriter::pageTitles; +QStringMap DoxWriter::externalPageTitles; +QStringMap DoxWriter::exampleTitlesInverse; +QStringMap DoxWriter::headerFileTitlesInverse; +QStringMap DoxWriter::fileTitlesInverse; +QStringMap DoxWriter::groupTitlesInverse; +QStringMap DoxWriter::moduleTitlesInverse; +QStringMap DoxWriter::pageTitlesInverse; +QStringMap DoxWriter::externalPageTitlesInverse; +QStringMultiMap DoxWriter::variables; +QStringMultiMap DoxWriter::properties; +QStringMultiMap DoxWriter::enums; +#endif + +Doc::Doc(const Location& start_loc, + const Location& end_loc, + const QString& source, + const QSet<QString>& metaCommandSet) +{ + priv = new DocPrivate(start_loc,end_loc,source); + DocParser parser; + parser.parse(source,priv,metaCommandSet); +#ifdef QDOC2DOX + if (DoxWriter::isDoxPass()) { + DoxWriter doxWriter(source,priv); + if (DoxWriter::isDoxPass(1)) + doxWriter.pass1(); + else + doxWriter.pass2(); + } +#endif +} + +Doc::Doc(const Doc& doc) + : priv(0) +{ + operator=(doc); +} + +Doc::~Doc() +{ + if (priv && priv->deref()) + delete priv; +} + +Doc &Doc::operator=(const Doc& doc) +{ + if (doc.priv) + doc.priv->ref(); + if (priv && priv->deref()) + delete priv; + priv = doc.priv; + return *this; +} + +void Doc::renameParameters(const QStringList &oldNames, + const QStringList &newNames) +{ + if (priv && oldNames != newNames) { + detach(); + + priv->params = newNames.toSet(); + + Atom *atom = priv->text.firstAtom(); + while (atom) { + if (atom->type() == Atom::FormattingLeft + && atom->string() == ATOM_FORMATTING_PARAMETER) { + atom = atom->next(); + if (!atom) + return; + int index = oldNames.indexOf(atom->string()); + if (index != -1 && index < newNames.count()) + atom->setString(newNames.at(index)); + } + atom = atom->next(); + } + } +} + +void Doc::simplifyEnumDoc() +{ + if (priv) { + if (priv->isEnumDocSimplifiable()) { + detach(); + + Text newText; + + Atom *atom = priv->text.firstAtom(); + while (atom) { + if ((atom->type() == Atom::ListLeft) && + (atom->string() == ATOM_LIST_VALUE)) { + while (atom && ((atom->type() != Atom::ListRight) || + (atom->string() != ATOM_LIST_VALUE))) + atom = atom->next(); + if (atom) + atom = atom->next(); + } else { + newText << *atom; + atom = atom->next(); + } + } + priv->text = newText; + } + } +} + +void Doc::setBody(const Text &text) +{ + detach(); + priv->text = text; +} + +/*! + Returns the starting location of a qdoc comment. + */ +const Location &Doc::location() const +{ + static const Location dummy; + return priv == 0 ? dummy : priv->start_loc; +} + +const QString &Doc::source() const +{ + static QString null; + return priv == 0 ? null : priv->src; +} + +bool Doc::isEmpty() const +{ + return priv == 0 || priv->src.isEmpty(); +} + +const Text& Doc::body() const +{ + static const Text dummy; + return priv == 0 ? dummy : priv->text; +} + +Text Doc::briefText() const +{ + return body().subText(Atom::BriefLeft, Atom::BriefRight); +} + +Text Doc::trimmedBriefText(const QString &className) const +{ + QString classNameOnly = className; + if (className.contains("::")) + classNameOnly = className.split("::").last(); + + Text originalText = briefText(); + Text resultText; + const Atom *atom = originalText.firstAtom(); + if (atom) { + QString briefStr; + QString whats; + bool standardWording = true; + + /* + This code is really ugly. The entire \brief business + should be rethought. + */ + while (atom && (atom->type() == Atom::AutoLink || atom->type() == Atom::String)) { + briefStr += atom->string(); + atom = atom->next(); + } + + QStringList w = briefStr.split(" "); + if (!w.isEmpty() && w.first() == "The") + w.removeFirst(); + else { + location().warning( + tr("Nonstandard wording in '\\%1' text for '%2' (expected 'The')") + .arg(COMMAND_BRIEF).arg(className)); + standardWording = false; + } + + if (!w.isEmpty() && (w.first() == className || w.first() == classNameOnly)) + w.removeFirst(); + else { + location().warning( + tr("Nonstandard wording in '\\%1' text for '%2' (expected '%3')") + .arg(COMMAND_BRIEF).arg(className).arg(className)); + standardWording = false; + } + + if (!w.isEmpty() && (w.first() == "class" || w.first() == "widget" + || w.first() == "namespace" || w.first() == "header")) + w.removeFirst(); + else { + location().warning( + tr("Nonstandard wording in '\\%1' text for '%2' (" + "expected 'class', 'widget', 'namespace' or 'header')") + .arg(COMMAND_BRIEF).arg(className)); + standardWording = false; + } + + if (!w.isEmpty() && (w.first() == "is" || w.first() == "provides")) + w.removeFirst(); + + if (!w.isEmpty() && (w.first() == "a" || w.first() == "an")) + w.removeFirst(); + + whats = w.join(" "); + if (whats.endsWith(".")) + whats.truncate(whats.length() - 1); + + if (whats.isEmpty()) { + location().warning( + tr("Nonstandard wording in '\\%1' text for '%2' (expected more text)") + .arg(COMMAND_BRIEF).arg(className)); + standardWording = false; + } else + whats[0] = whats[0].toUpper(); + + // ### move this once \brief is abolished for properties + if (standardWording) + resultText << whats; + } + return resultText; +} + +Text Doc::legaleseText() const +{ + if (priv == 0 || !priv->hasLegalese) + return Text(); + else + return body().subText(Atom::LegaleseLeft, Atom::LegaleseRight); +} + +const QString& Doc::baseName() const +{ + static QString null; + if (priv == 0 || priv->extra == 0) { + return null; + } else { + return priv->extra->baseName; + } +} + +Doc::SectioningUnit Doc::granularity() const +{ + if (priv == 0 || priv->extra == 0) { + return DocPrivateExtra().granularity; + } else { + return priv->extra->granularity; + } +} + +#if notyet // ### +Doc::SectioningUnit Doc::sectioningUnit() const +{ + if (priv == 0 || priv->extra == 0) { + return DocPrivateExtra().sectioningUnit; + } else { + return priv->extra->sectioningUnit; + } +} +#endif + +const QSet<QString> &Doc::parameterNames() const +{ + return priv == 0 ? *null_Set_QString() : priv->params; +} + +const QStringList &Doc::enumItemNames() const +{ + return priv == 0 ? *null_QStringList() : priv->enumItemList; +} + +const QStringList &Doc::omitEnumItemNames() const +{ + return priv == 0 ? *null_QStringList() : priv->omitEnumItemList; +} + +const QSet<QString> &Doc::metaCommandsUsed() const +{ + return priv == 0 ? *null_Set_QString() : priv->metacommandsUsed; +} + +QStringList Doc::metaCommandArgs(const QString& metacommand) const +{ + return priv == 0 ? QStringList() : priv->metaCommandMap.value(metacommand); +} + +const QList<Text> &Doc::alsoList() const +{ + return priv == 0 ? *null_QList_Text() : priv->alsoList; +} + +bool Doc::hasTableOfContents() const +{ + return priv && priv->extra && !priv->extra->tableOfContents.isEmpty(); +} + +bool Doc::hasKeywords() const +{ + return priv && priv->extra && !priv->extra->keywords.isEmpty(); +} + +bool Doc::hasTargets() const +{ + return priv && priv->extra && !priv->extra->targets.isEmpty(); +} + +const QList<Atom *> &Doc::tableOfContents() const +{ + priv->constructExtra(); + return priv->extra->tableOfContents; +} + +const QList<int> &Doc::tableOfContentsLevels() const +{ + priv->constructExtra(); + return priv->extra->tableOfContentsLevels; +} + +const QList<Atom *> &Doc::keywords() const +{ + priv->constructExtra(); + return priv->extra->keywords; +} + +const QList<Atom *> &Doc::targets() const +{ + priv->constructExtra(); + return priv->extra->targets; +} + +const QStringMap &Doc::metaTagMap() const +{ + return priv && priv->extra ? priv->extra->metaMap : *null_QStringMap(); +} + +void Doc::initialize(const Config& config) +{ + DocParser::tabSize = config.getInt(CONFIG_TABSIZE); + DocParser::exampleFiles = config.getStringList(CONFIG_EXAMPLES); + DocParser::exampleDirs = config.getStringList(CONFIG_EXAMPLEDIRS); + DocParser::sourceFiles = config.getStringList(CONFIG_SOURCES); + DocParser::sourceDirs = config.getStringList(CONFIG_SOURCEDIRS); + DocParser::quoting = config.getBool(CONFIG_QUOTINGINFORMATION); + + QStringMap reverseAliasMap; + + QSet<QString> commands = config.subVars(CONFIG_ALIAS); + QSet<QString>::ConstIterator c = commands.begin(); + while (c != commands.end()) { + QString alias = config.getString(CONFIG_ALIAS + Config::dot + *c); + if (reverseAliasMap.contains(alias)) { + config.lastLocation().warning(tr("Command name '\\%1' cannot stand" + " for both '\\%2' and '\\%3'") + .arg(alias) + .arg(reverseAliasMap[alias]) + .arg(*c)); + } else { + reverseAliasMap.insert(alias, *c); + } + aliasMap()->insert(*c, alias); + ++c; + } + + int i = 0; + while (cmds[i].english) { + cmds[i].alias = new QString(alias(cmds[i].english)); + cmdHash()->insert(*cmds[i].alias, cmds[i].no); + + if (cmds[i].no != i) + Location::internalError(tr("command %1 missing").arg(i)); + i++; + } + + QSet<QString> macroNames = config.subVars(CONFIG_MACRO); + QSet<QString>::ConstIterator n = macroNames.begin(); + while (n != macroNames.end()) { + QString macroDotName = CONFIG_MACRO + Config::dot + *n; + Macro macro; + macro.numParams = -1; + macro.defaultDef = config.getString(macroDotName); + if (!macro.defaultDef.isEmpty()) { + macro.defaultDefLocation = config.lastLocation(); + macro.numParams = Config::numParams(macro.defaultDef); + } + bool silent = false; + + QSet<QString> formats = config.subVars(macroDotName); + QSet<QString>::ConstIterator f = formats.begin(); + while (f != formats.end()) { + QString def = config.getString(macroDotName + Config::dot + *f); + if (!def.isEmpty()) { + macro.otherDefs.insert(*f, def); + int m = Config::numParams(macro.defaultDef); + if (macro.numParams == -1) { + macro.numParams = m; + } else if (macro.numParams != m) { + if (!silent) { + QString other = tr("default"); + if (macro.defaultDef.isEmpty()) + other = macro.otherDefs.begin().key(); + config.lastLocation().warning(tr("Macro '\\%1' takes" + " inconsistent number" + " of arguments (%2" + " %3, %4 %5)") + .arg(*n) + .arg(*f) + .arg(m) + .arg(other) + .arg(macro.numParams)); + silent = true; + } + if (macro.numParams < m) + macro.numParams = m; + } + } + ++f; + } + + if (macro.numParams != -1) + macroHash()->insert(*n, macro); + ++n; + } +} + +void Doc::terminate() +{ + DocParser::exampleFiles.clear(); + DocParser::exampleDirs.clear(); + DocParser::sourceFiles.clear(); + DocParser::sourceDirs.clear(); + aliasMap()->clear(); + cmdHash()->clear(); + macroHash()->clear(); + + int i = 0; + while (cmds[i].english) { + delete cmds[i].alias; + cmds[i].alias = 0; + ++i; + } +} + +QString Doc::alias(const QString &english) +{ + return aliasMap()->value(english, english); +} + +/*! + Trims the deadwood out of \a str. i.e., this function + cleans up \a str. + */ +void Doc::trimCStyleComment(Location& location, QString& str) +{ + QString cleaned; + Location m = location; + bool metAsterColumn = true; + int asterColumn = location.columnNo() + 1; + int i; + + for (i = 0; i < (int) str.length(); i++) { + if (m.columnNo() == asterColumn) { + if (str[i] != '*') + break; + cleaned += ' '; + metAsterColumn = true; + } + else { + if (str[i] == '\n') { + if (!metAsterColumn) + break; + metAsterColumn = false; + } + cleaned += str[i]; + } + m.advance(str[i]); + } + if (cleaned.length() == str.length()) + str = cleaned; + + for (int i = 0; i < 3; i++) + location.advance(str[i]); + str = str.mid(3, str.length() - 5); +} + +CodeMarker *Doc::quoteFromFile(const Location &location, + Quoter "er, + const QString &fileName) +{ + quoter.reset(); + + QString code; + + QString userFriendlyFilePath; + QString filePath = Config::findFile(location, + DocParser::exampleFiles, + DocParser::exampleDirs, + fileName, userFriendlyFilePath); + if (filePath.isEmpty()) { + location.warning(tr("Cannot find example file '%1'").arg(fileName)); + } else { + QFile inFile(filePath); + if (!inFile.open(QFile::ReadOnly)) { + location.warning(tr("Cannot open example file '%1'").arg(userFriendlyFilePath)); + } else { + QTextStream inStream(&inFile); + code = DocParser::untabifyEtc(inStream.readAll()); + } + } + + QString dirPath = QFileInfo(filePath).path(); + CodeMarker *marker = CodeMarker::markerForFileName(fileName); + quoter.quoteFromFile(userFriendlyFilePath, + code, + marker->markedUpCode(code, 0, dirPath)); + return marker; +} + +QString Doc::canonicalTitle(const QString &title) +{ + // The code below is equivalent to the following chunk, but _much_ + // faster (accounts for ~10% of total running time) + // + // QRegExp attributeExpr("[^A-Za-z0-9]+"); + // QString result = title.toLower(); + // result.replace(attributeExpr, " "); + // result = result.simplified(); + // result.replace(QLatin1Char(' '), QLatin1Char('-')); + + QString result; + result.reserve(title.size()); + + bool slurping = false; + bool begun = false; + int lastAlnum = 0; + for (int i = 0; i != title.size(); ++i) { + uint c = title.at(i).unicode(); + if (c >= 'A' && c <= 'Z') + c -= 'A' - 'a'; + bool alnum = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); + if (alnum) { + result += QLatin1Char(c); + begun = true; + slurping = false; + lastAlnum = result.size(); + } else if (!slurping) { + if (begun) + result += QLatin1Char('-'); + slurping = true; + } else { + // !alnum && slurping -> nothin + } + } + result.truncate(lastAlnum); + return result; +} + +void Doc::detach() +{ + if (!priv) { + priv = new DocPrivate; + return; + } + if (priv->count == 1) + return; + + --priv->count; + + DocPrivate *newPriv = new DocPrivate(*priv); + newPriv->count = 1; + if (priv->extra) + newPriv->extra = new DocPrivateExtra(*priv->extra); + + priv = newPriv; +} + +#ifdef QDOC2DOX +/*! + Sets the doxygen writer pass to \a pass. You can use + isDoxPass(), with or without a parameter, to test if + you are in a doxygen writer run or in a specific pass + of a doxygen writer run. + + This function is only called from main() if either the + \e doxygen1 or \e doxygen2 flag is passed to qdoc3 on + the command line. + */ +void DoxWriter::setDoxPass(int pass) +{ + qDebug() << "SETTING doxygen pass to " << pass + << " in DoxWriter::setDoxPass()"; + doxPass = pass; +} + +/*! + Returns true if the doxygen pass is set to \a pass, + which means we are in the specified \a pass of a doxygen + writer run of qdoc3. + */ +bool DoxWriter::isDoxPass(int pass) { return (doxPass == pass); } + +/*! + Returns true if the doxygen pass is 1 or 2, which + means this is a doxygen writer run to transform qdoc + comments into doxygen comments. + */ +bool DoxWriter::isDoxPass() { return (doxPass > 0); } + +bool DoxWriter::conversionRequired() const +{ + /* + Loop through all the topic commands searching for + one that must be transformed to doxygen format. If + one is found, return true. + */ + QCommandMap::const_iterator i; + i = priv->metaCommandMap.constBegin(); + while (i != priv->metaCommandMap.constEnd()) { + QString s = i.key(); + if (s == "enum") + return true; + else if (s == "example") + return true; + else if (s == "externalpage") + return true; + else if (s == "group") + return true; + else if (s == "headerfile") + return true; + else if (s == "module") + return true; + else if (s == "page") + return true; + else if (s == "property") + return true; + else if (s == "typedef") + return true; + else if (s == "variable") + return true; + else if (s == "overload") + return true; + else if (s == "reimp") + return true; + else if (s == "relates") + return true; + else if (s == "macro") + return true; + else { +#if 0 + if (s == "class") + else if (s == "namespace") + else if (s == "service") + else if (s == "inheaderfile") + else if (s == "file") + else if (s == "fn") + else if (s == "contentspage") + else if (s == "nextpage") + else if (s == "previous") + else if (s == "indexpage") + else if (s == "startpage") +#endif + } + ++i; + } + + /* + Loop through all the qdoc atoms searching for one + that must be transformed to doxygen format. If one + is found, return true. + */ + const Atom* next = priv->text.firstAtom(); + while (next != 0) { + Atom::Type atomType = next->type(); + switch (atomType) { + case Atom::C: + case Atom::CaptionLeft: + case Atom::Code: + case Atom::CodeBad: + case Atom::CodeNew: + case Atom::CodeOld: + case Atom::CodeQuoteArgument: + case Atom::CodeQuoteCommand: + case Atom::FootnoteLeft: + case Atom::FormatElse: + case Atom::FormatEndif: + case Atom::FormatIf: + case Atom::GeneratedList: + case Atom::Image: + case Atom::ImageText: + case Atom::InlineImage: + case Atom::LegaleseLeft: + case Atom::LineBreak: + case Atom::Link: + case Atom::LinkNode: + case Atom::ListLeft: + case Atom::ListItemNumber: + case Atom::ListTagLeft: + case Atom::ListItemLeft: + case Atom::QuotationLeft: + case Atom::RawString: + case Atom::SectionLeft: + case Atom::SectionHeadingLeft: + case Atom::SidebarLeft: + case Atom::SnippetCommand: + case Atom::SnippetIdentifier: + case Atom::SnippetLocation: + case Atom::TableLeft: + case Atom::TableHeaderLeft: + case Atom::TableRowLeft: + case Atom::TableItemLeft: + case Atom::TableOfContents: + case Atom::Target: + return true; + case Atom::AbstractLeft: + case Atom::AbstractRight: + case Atom::AutoLink: + case Atom::BaseName: + case Atom::BriefLeft: + case Atom::BriefRight: + case Atom::CaptionRight: + case Atom::FormattingLeft: + case Atom::FormattingRight: + case Atom::Nop: + case Atom::ParaLeft: + case Atom::ParaRight: + case Atom::FootnoteRight: + case Atom::LegaleseRight: + case Atom::ListTagRight: + case Atom::ListItemRight: + case Atom::ListRight: + case Atom::QuotationRight: + case Atom::SectionRight: + case Atom::SectionHeadingRight: + case Atom::SidebarRight: + case Atom::String: + case Atom::TableRight: + case Atom::TableHeaderRight: + case Atom::TableRowRight: + case Atom::TableItemRight: + default: + break; + } + next = next->next(); + } + return false; +} + +/*! + A convenience function to write a qdoc metacommand as a + doxygen command, without conversion. i.e., some of the + qdoc metacommands don't require conversion for doxygen. + */ +void DoxWriter::writeCommand(QCommandMap::const_iterator cmd) +{ + concatenate("\\" + cmd.key() + " " + cmd.value()[0]); + newLine(); +} + +/*! + Convert the qdoc commands in the metacommand map to + doxygen format. This function is called only in pass2(). + The metacommand map contains all the metacommands that + were found in the qdoc comment that is being converted. + The metacommands are the ones that begin with the '\'. + These are not considered part of the text of the comment. + The text is converted by convertText(). + */ +void DoxWriter::convertMetaCommands() +{ + QCommandMap& metaCmdMap = priv->metaCommandMap; + QCommandMap::iterator cmd; + int c; + + currentPage.clear(); + currentFn.clear(); + currentTitle.clear(); + currentEnum.clear(); + currentProperty.clear(); + currentVariable.clear(); + currentClass.clear(); + currentExample.clear(); + currentGroup.clear(); + currentModule.clear(); + currentMacro.clear(); + currentService.clear(); + currentTypedef.clear(); + currentHeaderFile.clear(); + commentType = OtherComment; + + if ((cmd = metaCmdMap.find("class")) != metaCmdMap.end()) { + currentClass = cmd.value()[0]; + if ((c = currentClass.indexOf(' ')) > 0) + currentClass = currentClass.left(c); + writeCommand(cmd); + metaCmdMap.erase(cmd); + commentType = ClassComment; + } + else if ((cmd = metaCmdMap.find("fn")) != metaCmdMap.end()) { + currentFn = cmd.value()[0]; + writeCommand(cmd); + metaCmdMap.erase(cmd); + commentType = FnComment; + } + else if ((cmd = metaCmdMap.find("enum")) != metaCmdMap.end()) { + currentEnum = cmd.value()[0]; + if ((c = currentEnum.lastIndexOf("::")) > 0) { + currentClass = currentEnum.left(c); + currentEnum = currentEnum.right(currentEnum.size()-c-2); + qDebug() << "currentEnum =" << currentEnum; + qDebug() << "currentClass =" << currentClass; + } + writeCommand(cmd); + metaCmdMap.erase(cmd); + commentType = EnumComment; + } + else if ((cmd = metaCmdMap.find("property")) != metaCmdMap.end()) { + currentClass = cmd.value()[0]; + if ((c = currentClass.lastIndexOf("::")) > 0) { + currentProperty = currentClass.right(currentClass.size()-c-2); + currentClass = currentClass.left(c); + qDebug() << "currentProperty =" << currentProperty; + qDebug() << "currentClass =" << currentClass; + } + writeCommand(cmd); + metaCmdMap.erase(cmd); + commentType = PropertyComment; + } + else if ((cmd = metaCmdMap.find("variable")) != metaCmdMap.end()) { + currentClass = cmd.value()[0]; + if ((c = currentClass.lastIndexOf("::")) > 0) { + currentVariable = currentClass.right(currentClass.size()-c-2); + currentClass = currentClass.left(c); + qDebug() << "currentVariable =" << currentVariable; + qDebug() << "currentClass =" << currentClass; + } + concatenate("\\var " + cmd.value()[0]); + newLine(); + metaCmdMap.erase(cmd); + commentType = VariableComment; + } + + if ((cmd = metaCmdMap.find("page")) != metaCmdMap.end()) { + currentPage = cmd.value()[0]; + QString htmlFile = currentPage; + const QString* title = getPageTitle(htmlFile); + QStringList parts = htmlFile.split('.'); + metaCmdMap.erase(cmd); + if (title) { + concatenate("\\page " + parts[0] + " " + *title); + newLine(); + } + commentType = PageComment; + qDebug() << "currentPage =" << currentPage; + } + + if ((cmd = metaCmdMap.find("example")) != metaCmdMap.end()) { + currentExample = cmd.value()[0]; + metaCmdMap.erase(cmd); + commentType = ExampleComment; + qDebug() << "currentExample =" << currentExample; + } + + if ((cmd = metaCmdMap.find("macro")) != metaCmdMap.end()) { + currentMacro = cmd.value()[0]; + metaCmdMap.erase(cmd); + commentType = MacroComment; + qDebug() << "currentMacro =" << currentMacro; + } + + if ((cmd = metaCmdMap.find("group")) != metaCmdMap.end()) { + currentGroup = cmd.value()[0]; + metaCmdMap.erase(cmd); + commentType = GroupComment; + qDebug() << "currentGroup =" << currentGroup; + } + + if ((cmd = metaCmdMap.find("module")) != metaCmdMap.end()) { + currentModule = cmd.value()[0]; + metaCmdMap.erase(cmd); + commentType = ModuleComment; + qDebug() << "currentModule =" << currentModule; + } + + if ((cmd = metaCmdMap.find("headerfile")) != metaCmdMap.end()) { + currentHeaderFile = cmd.value()[0]; + metaCmdMap.erase(cmd); + commentType = HeaderFileComment; + qDebug() << "currentHeaderFile =" << currentHeaderFile; + } + + if ((cmd = metaCmdMap.find("typedef")) != metaCmdMap.end()) { + currentClass = cmd.value()[0]; + if ((c = currentClass.lastIndexOf("::")) > 0) { + currentTypedef = currentClass.right(currentClass.size()-c-2); + currentClass = currentClass.left(c); + } + metaCmdMap.erase(cmd); + commentType = TypedefComment; + qDebug() << "currentTypedef =" << currentTypedef; + qDebug() << "currentClass =" << currentClass; + } + + cmd = priv->metaCommandMap.begin(); + while (cmd != priv->metaCommandMap.end()) { + for (int i=0; i<cmd.value().size(); i++) { + concatenate("\\" + cmd.key() + " " + cmd.value()[i]); + newLine(); + } + //qDebug() << " " << cmd.key() << ": " << cmd.value(); + ++cmd; + } +} + +/*! + Convert the qdoc text to doxygen format. The metacommands + are converted by convertMetaCommands(). This function is + called in pass2(). + */ +void DoxWriter::convertText() +{ + const Atom* prev = 0; + const Atom* next = priv->text.firstAtom(); + while (next != 0) { + next->dump(); + Atom::Type atomType = next->type(); + switch (atomType) { + case Atom::AbstractLeft: + break; + case Atom::AbstractRight: + break; + case Atom::AutoLink: + concatenate(next->string()); + break; + case Atom::BaseName: + break; + case Atom::BriefLeft: + concatenate("\\brief "); + break; + case Atom::BriefRight: + newLine(); + break; + case Atom::C: + tt(next); + break; + case Atom::CaptionLeft: + unhandled(next); + break; + case Atom::CaptionRight: + unhandled(next); + break; + case Atom::Code: + code(next); + break; + case Atom::CodeBad: + code(next); + break; + case Atom::CodeNew: + newLine(); + concatenate("you can rewrite it as"); + code(next); + break; + case Atom::CodeOld: + newLine(); + concatenate("For example, if you have code like"); + code(next); + break; + case Atom::CodeQuoteArgument: + unhandled(next); + break; + case Atom::CodeQuoteCommand: + next = codeQuoteCommand(next); + break; + case Atom::FootnoteLeft: + break; + case Atom::FootnoteRight: + break; + case Atom::FormatElse: + formatElse(); + break; + case Atom::FormatEndif: + formatEndif(); + break; + case Atom::FormatIf: + formatIf(next); + break; + case Atom::FormattingLeft: + formattingLeft(next,next->next()); + break; + case Atom::FormattingRight: + formattingRight(next,prev); + break; + case Atom::GeneratedList: + break; + case Atom::Image: + break; + case Atom::ImageText: + break; + case Atom::InlineImage: + break; + case Atom::LegaleseLeft: + break; + case Atom::LegaleseRight: + break; + case Atom::LineBreak: + break; + case Atom::Link: + next = link(next); + break; + case Atom::LinkNode: + break; + case Atom::ListLeft: + { + bool nested = false; + if (structs.isEmpty()) { + const Atom* i = next->next(); + while (i->type() != Atom::ListRight) { + if ((i->type() == Atom::ListLeft) || + (i->type() == Atom::TableLeft)) { + nested = true; + break; + } + i = i->next(); + } + } + else + nested = true; + StructDesc d(BulletList,nested); + if (next->string() == "numeric") + d.structType = NumericList; + else if (next->string() == "value") { + d.structType = ValueList; + } + else if (next->string() != "bullet") + qDebug() << "UNKNOWN LIST TYPE" << next->string(); + structs.push(d); + if (nested || (d.structType != BulletList)) { + if (d.structType == BulletList) + concatenate("<ul>"); + else if (d.structType == NumericList) + concatenate("<ol>"); + else if (d.structType == ValueList) + concatenate("<dl>"); + newLine(); + } + } + break; + case Atom::ListItemNumber: + structs.top().count = next->string().toInt(); + break; + case Atom::ListTagLeft: + { + structs.top().count++; + concatenate("<dt>"); + const Atom* n = next->next(); + if (n->type() == Atom::String) { + qDebug() << "ENUM VALUE" << n->string(); + } + else + qDebug() << "NOT EN ENUM"; + } + break; + case Atom::ListTagRight: + concatenate("</dt>"); + break; + case Atom::ListItemLeft: + { + newLine(); + const StructDesc& d = structs.top(); + if (d.structType == BulletList) { + if (!d.nested) + concatenate("\\arg "); + else + concatenate("<li>"); + } + else if (d.structType == NumericList) + concatenate("<li>"); + else if (d.structType == ValueList) + concatenate("<dd>"); + } + break; + case Atom::ListItemRight: + { + const StructDesc& d = structs.top(); + if (d.structType == BulletList) { + if (d.nested) { + concatenate("</li>"); + newLine(); + } + } + else if (d.structType == NumericList) { + concatenate("</li>"); + newLine(); + } + else if (d.structType == ValueList) { + concatenate("</dd>"); + newLine(); + } + } + break; + case Atom::ListRight: + { + if (!structs.isEmpty()) { + const StructDesc& d = structs.top(); + if (d.nested || (d.structType != BulletList)) { + if (d.structType == BulletList) + concatenate("</ul>"); + else if (d.structType == NumericList) + concatenate("</ol>"); + else if (d.structType == ValueList) + concatenate("</dl>"); + newLine(); + } + structs.pop(); + } + } + break; + case Atom::Nop: + // nothing. + break; + case Atom::ParaLeft: + if (structs.isEmpty()) + newLine(); + break; + case Atom::ParaRight: + { + if (structs.isEmpty()) + newLine(); + else { + const StructDesc& d = structs.top(); + if (d.nested || (d.structType != BulletList)) { + Atom::Type t = next->next()->type(); + if ((t != Atom::ListItemRight) && + (t != Atom::TableItemRight)) + newLine(); + } + else + newLine(); + } + } + break; + case Atom::QuotationLeft: + break; + case Atom::QuotationRight: + break; + case Atom::RawString: + concatenate(next->string()); + break; + case Atom::SectionLeft: + // nothing. + break; + case Atom::SectionRight: + // nothing. + break; + case Atom::SectionHeadingLeft: + next = sectionHeading(next); + break; + case Atom::SectionHeadingRight: + newLine(); + break; + case Atom::SidebarLeft: + break; + case Atom::SidebarRight: + break; + case Atom::SnippetCommand: + newLine(); + concatenate("\\snippet "); + break; + case Atom::SnippetIdentifier: + newText += next->string(); + lineLength += next->string().size(); + newLine(); + break; + case Atom::SnippetLocation: + newText += next->string() + " "; + lineLength += next->string().size() + 1; + break; + case Atom::String: + wrap(next->string()); + break; + case Atom::TableLeft: + { + bool nested = false; + if (structs.isEmpty()) { + const Atom* i = next->next(); + while (i->type() != Atom::TableRight) { + if ((i->type() == Atom::ListLeft) || + (i->type() == Atom::TableLeft)) { + nested = true; + break; + } + i = i->next(); + } + } + else + nested = true; + StructDesc d(Table,nested); + structs.push(d); + if (next->string().isEmpty()) + concatenate("<table>"); + else { + QString attrs = "width=\"" + next->string() + "\""; + attrs += " align=\"center\""; + concatenate("<table " + attrs + ">"); + } + newLine(); + } + break; + case Atom::TableRight: + concatenate("</table>"); + if (!structs.isEmpty()) + structs.pop(); + newLine(); + break; + case Atom::TableHeaderLeft: + concatenate("<tr>"); + if (!structs.isEmpty()) + structs.top().inTableHeader = true; + newLine(); + break; + case Atom::TableHeaderRight: + concatenate("</tr>"); + if (!structs.isEmpty()) + structs.top().inTableHeader = false; + newLine(); + break; + case Atom::TableRowLeft: + if (!structs.isEmpty()) { + structs.top().inTableRow = true; + concatenate("<tr valign=\"top\" class=\""); + if (structs.top().odd) + concatenate("odd\">"); + else + concatenate("even\">"); + structs.top().odd = !structs.top().odd; + } + newLine(); + break; + case Atom::TableRowRight: + concatenate("</tr>"); + if (!structs.isEmpty()) + structs.top().inTableRow = false; + newLine(); + break; + case Atom::TableItemLeft: + if (!structs.isEmpty()) { + structs.top().inTableItem = true; + concatenate("<td>"); + if (structs.top().inTableHeader) + concatenate("<b> "); + } + break; + case Atom::TableItemRight: + if (!structs.isEmpty()) { + structs.top().inTableItem = false; + if (structs.top().inTableHeader) + concatenate(" </b>"); + concatenate("</td>"); + } + newLine(); + break; + case Atom::TableOfContents: + break; + case Atom::Target: + { + QString text = next->string(); + text.remove(ws_rx); + newLine(); + concatenate("\\anchor "); + newText += text; + lineLength += text.size(); + newLine(); + } + break; + case Atom::UnhandledFormat: + unhandled(next); + break; + case Atom::UnknownCommand: + unhandled(next); + break; + default: + //next->dump(); + break; + } + prev = next; + next = next->next(); + } +} + +/*! + + Pass one looks for topic commands and target and section + commands, and maybe other stuff. These are serialized to + text files, which are read back in by pass2(). + */ +void DoxWriter::pass1() +{ + QCommandMap& metaCmdMap = priv->metaCommandMap; + if (!metaCmdMap.isEmpty()) { + int c; + QCommandMap::iterator cmd; + if ((cmd = metaCmdMap.find("enum")) != metaCmdMap.end()) { + commentType = EnumComment; + currentEnum = cmd.value()[0]; + if ((c = currentEnum.lastIndexOf("::")) > 0) { + currentClass = currentEnum.left(c); + currentEnum = currentEnum.right(currentEnum.size()-c-2); + qDebug() << "currentEnum =" << currentEnum; + qDebug() << "currentClass =" << currentClass; + if (enums.contains(currentEnum,currentClass)) { + qWarning() << "DoxWriter::pass1():" + << "Duplicate enum:" + << currentClass << currentEnum; + } + else + enums.insert(currentEnum,currentClass); + } + } + else if ((cmd = metaCmdMap.find("property")) != metaCmdMap.end()) { + commentType = PropertyComment; + currentClass = cmd.value()[0]; + if ((c = currentClass.lastIndexOf("::")) > 0) { + currentProperty = currentClass.right(currentClass.size()-c-2); + currentClass = currentClass.left(c); + qDebug() << "currentProperty =" << currentProperty; + qDebug() << "currentClass =" << currentClass; + if (properties.contains(currentProperty,currentClass)) { + qWarning() << "DoxWriter::pass1():" + << "Duplicate property:" + << currentClass << currentProperty; + } + else + properties.insert(currentProperty,currentClass); + } + } + else if ((cmd = metaCmdMap.find("variable")) != metaCmdMap.end()) { + commentType = VariableComment; + currentClass = cmd.value()[0]; + if ((c = currentClass.lastIndexOf("::")) > 0) { + currentVariable = currentClass.right(currentClass.size()-c-2); + currentClass = currentClass.left(c); + qDebug() << "currentVariable =" << currentVariable; + qDebug() << "currentClass =" << currentClass; + if (variables.contains(currentVariable,currentClass)) { + qWarning() << "DoxWriter::pass1():" + << "Duplicate variable:" + << currentClass << currentVariable; + } + else + variables.insert(currentVariable,currentClass); + } + } + } + + /* + */ + const Atom* next = priv->text.firstAtom(); + while (next != 0) { + switch (next->type()) { + case Atom::SectionHeadingLeft: + { + QString text; + next = next->next(); + while (next) { + if (next->type() == Atom::SectionHeadingRight) + break; + else + text += next->string(); + next = next->next(); + } + //text.remove(ws_rx); + insertAnchor(text); + } + break; + case Atom::Target: + { + QString text = next->string(); + //text.remove(ws_rx); + insertAnchor(text); + } + default: + break; + } + next = next->next(); + } +} + +/*! + Output a parsed, tokenized qdoc comment as a doxygen + comment in diff format for input to the patch command. + */ +void DoxWriter::pass2() +{ + if (!conversionRequired()) { + qDebug() << "NO CONVERSION - FILE:" << priv->start_loc.fileName() + << "START:" << priv->start_loc.lineNo() + << "END:" << priv->end_loc.lineNo() - 1; + return; + } + + /* + Transformation to doxygen required... + */ + newText = "\n/*! \n"; + convertMetaCommands(); + convertText(); + if (newText[newText.size()-1] == ' ') + newText.remove(newText.size()-1,1); + newText += " */\n"; + qDebug() << "CONVERTED COMMENT - FILE:" << priv->start_loc.fileName() + << "START:" << priv->start_loc.lineNo() + << "END:" << priv->end_loc.lineNo() - 1; + qDebug() << newText; +} + +/*! + Unparse the second parameter of a "\l" command. + */ +const Atom* DoxWriter::link(const Atom* atom) +{ + QString first_text = atom->string(); + QString second_text; + const QString* value = 0; + + const Atom* next = atom->next(Atom::FormattingLeft,Atom::LINK_); + if (next) { + next->dump(); + while (1) { + next = next->next(); + next->dump(); + if (next->type() == Atom::FormattingRight) { + if (next->string() == Atom::LINK_) + break; + else { + // ignore it. + } + } + else + second_text += next->string(); + } + int i = first_text.indexOf('#'); + if (i >= 0) + first_text = first_text.right(first_text.size() - i - 1); + //newLine(); + if ((value = getExternalPage(first_text))) { + //qDebug() << "USED AN EXTERNAL PAGE TITLE" << first_text; + QString href = "<a href=\""+*value+"\">"+first_text+"</a>"; + concatenate(href); + } + else if (first_text.startsWith("http:",Qt::CaseInsensitive)) { + if (first_text == second_text) { + concatenate(first_text); + } + else { + QString href = "<a href=\""+first_text+"\">"+second_text+"</a>"; + concatenate(href); + } + } + else if ((value = getPageFile(first_text))) { + //qDebug() << "USED A PAGE TITLE" << first_text; + QStringList parts = (*value).split('.'); + QString ref = "\\ref " + parts[0] + " \"" + second_text + "\""; + concatenate(ref); + } + else if ((value = getGroup(first_text))) { + //qDebug() << "USED A GROUP TITLE" << first_text; + concatenate("\\ref " + *value + " \"" + second_text + "\""); + } + else if ((value = getModule(first_text))) { + //qDebug() << "USED A MODULE TITLE" << first_text; + concatenate("\\ref " + *value + " \"" + second_text + "\""); + } + else if ((value = getExamplePath(first_text))) { + //qDebug() << "USED AN EXAMPLE TITLE" << first_text; + first_text.remove(ws_rx); + QString ref = "\\ref " + first_text + " \"" + second_text + "\""; + concatenate(ref); + } + else if ((value = getFile(first_text))) { + //qDebug() << "USED A FILE TITLE" << first_text; + // I think this command is no longer available. + first_text.remove(ws_rx); + QString ref = "\\ref " + first_text + " \"" + second_text + "\""; + concatenate(ref); + } + else if ((value = getHeaderFile(first_text))) { + //qDebug() << "USED A HEADER FILE TITLE" << first_text; + first_text.remove(ws_rx); + QString ref = "\\ref " + first_text + " \"" + second_text + "\""; + concatenate(ref); + } + else if (isAnchor(first_text)) { + //qDebug() << "USED AN ANCHOR" << first_text; + first_text.remove(ws_rx); + QString ref = "\\ref " + first_text + " \"" + second_text + "\""; + concatenate(ref); + } + else if ((value = getPageTitle(first_text))) { + //qDebug() << "USED AN INVERSE PAGE TITLE" << first_text; + QStringList parts = first_text.split('.'); + QString ref = "\\ref " + parts[0] + " \"" + second_text + "\""; + concatenate(ref); + } + else if ((value = getExampleTitle(first_text))) { + //qDebug() << "USED AN INVERSE EXAMPLE TITLE" << first_text; + QString title = *value; + title.remove(ws_rx); + QString ref = "\\ref " + title + " \"" + second_text + "\""; + concatenate(ref); + } + else if ((value = getGroupTitle(first_text))) { + //qDebug() << "USED AN INVERSE GROUP TITLE" << first_text; + concatenate("\\ref " + first_text + " \"" + second_text + "\""); + } + else if ((value = getModuleTitle(first_text))) { + //qDebug() << "USED AN INVERSE MODULE TITLE" << first_text; + concatenate("\\ref " + first_text + " \"" + second_text + "\""); + } + else if ((value = getFileTitle(first_text))) { + qDebug() << "USED AN INVERSE FILE TITLE" << first_text; + } + else if ((value = getHeaderFileTitle(first_text))) { + qDebug() << "USED AN INVERSE HEADER FILE TITLE" << first_text; + } + else if ((first_text.indexOf("::") >= 0) || + (first_text.indexOf("()") >= 0) || + (first_text[0] == 'Q')) { + //qDebug() << "AUTO-LINKABLE" << first_text; + if (first_text == second_text) + concatenate(first_text); + else { + QString link = first_text + " " + second_text; + concatenate("\\link " + link + "\\endlink"); + } + } + else { + QString link; + QStringList propertyClasses; + QStringList variableClasses; + QStringList enumClasses; + bool p = isProperty(first_text,propertyClasses); + bool v = isVariable(first_text,variableClasses); + bool e = isEnum(first_text,enumClasses); + if (e) { + if (enumClasses.size() == 1) + link = enumClasses[0]; + else if (enumClasses.contains(currentClass)) + link = currentClass; + else { + QString msg = "Unqualified enum name: " + first_text; + QString details = "Classes: " + enumClasses.join(", "); + priv->start_loc.error(msg,details); + } + if (!link.isEmpty()) + qDebug() << "FOUND ENUM" << link << first_text; + } + else if (p && v) { + if (propertyClasses.size() == 1) { + if (variableClasses.size() == 1) { + if (propertyClasses[0] == variableClasses[0]) + link = propertyClasses[0]; + } + } + if (link.isEmpty()) { + if (propertyClasses.contains(currentClass) || + variableClasses.contains(currentClass)) + link = currentClass; + else { + propertyClasses += variableClasses; + QString msg = "Unqualified property or variable name: " + + first_text; + QString details = "Classes: " + + propertyClasses.join(", "); + priv->start_loc.error(msg,details); + } + } + } + else if (p) { + if (propertyClasses.size() == 1) + link = propertyClasses[0]; + else if (propertyClasses.contains(currentClass)) + link = currentClass; + else { + QString msg = "Unqualified property name: " + first_text; + QString details = "Classes: " + propertyClasses.join(", "); + priv->start_loc.error(msg,details); + } + } + else if (v) { + if (variableClasses.size() == 1) + link = variableClasses[0]; + else if (variableClasses.contains(currentClass)) + link = currentClass; + else { + QString msg = "Unqualified variable name: " + first_text; + QString details = "Classes: " + variableClasses.join(", "); + priv->start_loc.error(msg,details); + } + } + else { + qDebug() << "NOT AUTO-LINKABLE" << first_text; + QString s = first_text + " " + second_text; + concatenate("\\link " + s + "\\endlink"); + } + if (!link.isEmpty()) { + link += "::" + first_text + " " + second_text; + concatenate("\\link " + link + "\\endlink"); + } + } + } + else + qDebug() << "LINK with no second parameter!!!!"; + return next? next : atom; +} + +/*! + If the current line length is 0, the current line is + indented according to the context. + */ +void DoxWriter::indentLine() +{ + if (lineLength == 0) { + newText += DOXYGEN_INDENT_STRING; + lineLength = DOXYGEN_INDENT; + if (!structs.isEmpty()) { + for (int i=1; i<structs.size(); ++i) { + newText += DOXYGEN_TAB_STRING; + lineLength += DOXYGEN_TAB_SIZE; + } + } + } +} + +/*! + Concatenates a newline to the doxygen text, increments the + line count, and resets the line length to 0. + */ +void DoxWriter::newLine() +{ + newText += "\n"; + ++lineCount; + lineLength = 0; +} + +static const int maxLineLength = 70; + +/*! + Concatenate the \a text to the doxygen comment currently + under construction and increment the current line length + by the size of the \a text. + + If incrementing the current line length by the \a text size + would make the current line length longer than the maximum + line length, then call newLine() and indentLine() \e before + concatenating the \a text. + */ +void DoxWriter::concatenate(QString text) +{ + if ((lineLength + text.size()) > maxLineLength) + newLine(); + indentLine(); + newText += text; + lineLength += text.size(); +} + +static bool punctuation(QChar c) +{ + switch (c.toAscii()) { + case '.': + case ',': + case ':': + case ';': + case '/': + case '+': + case '-': + case '?': + case '!': + case '\"': + return true; + default: + break; + } + return false; +} + +/*! + Concatenate the \a text string to the doxygen text, doing + line wrapping where necessary. + */ +void DoxWriter::wrap(QString text) +{ + int from = 0; + int to = -1; + + if ((lineLength == 0) || (lineLength >= maxLineLength)) { + if (!text.isEmpty() && (text[0] == ' ')) + text = text.right(text.size() - 1); + } + + indentLine(); + while (text.size()) { + int avail = maxLineLength - lineLength; + from = text.indexOf(' ',from); + if (from >= 0) { + if (from < avail) + to = from++; + else if (from == 1 && punctuation(text[0])) + to = from++; + else { + if (to >= 0) { + newText += text.left(to+1); + lineLength += to + 1; + text = text.right(text.size() - to - 1); + } + else { + newLine(); + indentLine(); + newText += text.left(from+1); + lineLength += from + 1; + text = text.right(text.size() - from - 1); + } + from = 0; + to = -1; + if (text.size() && (lineLength > maxLineLength)) { + newLine(); + indentLine(); + } + } + } + else + break; + } + if (text.size()) { + if (lineLength >= maxLineLength) { + newLine(); + indentLine(); + } + newText += text; + lineLength += text.size(); + } +} + +/*! + This will output something, but it depends on what the + \a atom string and the \a next atom string are. + */ +void DoxWriter::formattingLeft(const Atom* atom, const Atom* next) +{ + if (atom->string() == "parameter") { + concatenate("\\a "); + return; + } + else if (atom->string() == "underline") { + concatenate("<u>"); + return; + } + else if (atom->string() == "superscript") { + concatenate("<sup>"); + return; + } + else if (atom->string() == "subscript") { + concatenate("<sub>"); + return; + } + int ws = -1; + if (next) + ws = next->string().indexOf(ws_rx); + if (atom->string() == "bold") { + if (ws < 0) + concatenate("\\b "); + else + concatenate("<b>"); + } + else if (atom->string() == "italic") { + if (ws < 0) + concatenate("\\e "); + else + concatenate("<i>"); + } + else if (atom->string() == "teletype") { + if (ws < 0) + concatenate("\\c "); + else + concatenate("<tt>"); + } + else + qDebug() << "UNHANDLED FormattingLeft: " << atom->string(); +} + +/*! + This will output something, but it depends on what the + \a atom string and the \a prev atom string are. + */ +void DoxWriter::formattingRight(const Atom* atom, const Atom* prev) +{ + if (atom->string() == "parameter") + return; + else if (atom->string() == "underline") { + concatenate("</u>"); + return; + } + else if (atom->string() == "superscript") { + concatenate("</sup>"); + return; + } + else if (atom->string() == "subscript") { + concatenate("</sub>"); + return; + } + int ws = -1; + if (prev) + ws = prev->string().indexOf(ws_rx); + if (ws < 0) + return; + if (atom->string() == "bold") + concatenate("</b>"); + else if (atom->string() == "italic") + concatenate("</i>"); + else if (atom->string() == "teletype") + concatenate("</tt>"); + else + qDebug() << "UNHANDLED FormattingRight: " << atom->string(); +} + +/*! + Output a \c or a <tt>...</tt>. + */ +void DoxWriter::tt(const Atom* atom) +{ + if (atom->string().indexOf(ws_rx) < 0) { + concatenate("\\c "); + concatenate(atom->string()); + } + else { + concatenate("<tt>"); + concatenate(atom->string()); + concatenate("</tt>"); + } +} + +/*! + */ +void DoxWriter::formatIf(const Atom* atom) +{ + if (atom->string() == "HTML") { + newLine(); + concatenate("\\htmlonly"); + newLine(); + } +} + +/*! + */ +void DoxWriter::formatEndif() +{ + newLine(); + concatenate("\\endhtmlonly"); + newLine(); +} + +/*! + */ +void DoxWriter::formatElse() +{ + // nothing. +} + +/*! + Pass 1: Construct a section identifier and insert it into + the anchor set. + + Pass 2: Convert section1, section2, and section3 commands + to section, subsection, and subsubsection respectively. + Warn if a section command higher than 3 is seen. + */ +const Atom* DoxWriter::sectionHeading(const Atom* atom) +{ + QString heading_level = atom->string(); + QString heading_text; + const Atom* next = atom->next(); + while (next) { + next->dump(); + if (next->type() == Atom::SectionHeadingRight) { + if (next->string() == heading_level) + break; + else { + qDebug() << "WRONG SectionHeading number!!!!"; + } + } + else + heading_text += next->string(); + next = next->next(); + } + + QString heading_identifier = heading_text; + heading_identifier.remove(ws_rx); + + newLine(); + if (heading_level == "1") + heading_level = "\\section "; + else if (heading_level == "2") + heading_level = "\\subsection "; + else if (heading_level == "3") + heading_level = "\\subsubsection "; + else if (heading_level == "4") { + heading_level = "\\subsubsection "; + qDebug() << "WARNING section4 converted to \\subsubsection"; + } + else { + heading_level = "\\subsubsection "; + qDebug() << "WARNING section5 converted to \\subsubsection"; + } + concatenate(heading_level); + newText += heading_identifier + " "; + lineLength += heading_identifier.size() + 1; + newText += heading_text; + lineLength += heading_text.size(); + newLine(); + return next? next : atom; +} + +/*! + Report an unhandled atom. + */ +void DoxWriter::unhandled(const Atom* atom) +{ + qDebug() << "UNHANDLED ATOM"; + atom->dump(); +} + +/*! + Output a code/endcode block. + */ +void DoxWriter::code(const Atom* atom) +{ + newLine(); + concatenate("\\code"); + writeCode(atom->string()); + concatenate("\\endcode"); + newLine(); +} + +/*! + Output a code/endcode block depending on the + CodeQuote Command and CodeQuoteArgument parameters. + */ +const Atom* DoxWriter::codeQuoteCommand(const Atom* atom) +{ + QString command = atom->string(); + atom = atom->next(); + concatenate("\\code"); + if (command == "codeline") { + newLine(); + concatenate(atom->string()); + newLine(); + } + else if (command == "dots") { + newLine(); + concatenate(atom->string()); + newLine(); + } + else { + writeCode(atom->string()); + } + concatenate("\\endcode"); + return atom; +} + +/*! + Appends a block of code to the comment. + */ +void DoxWriter::writeCode(QString text) +{ + int cr_count = text.count('\n') - 1; + if (cr_count >= 0) { + int last_cr = text.lastIndexOf('\n'); + newText += text.left(last_cr); + lineCount += cr_count; + } + else + newText += text; + newLine(); +} + +/*! + Inserts \a text into the anchor set. This function is called + during doxygen pass 1. + */ +void DoxWriter::insertAnchor(const QString& text) +{ + anchors.insert(text); +} + +/*! + Returns true if \a text identifies an anchor, section, + subsection, subsubsection, or page. + */ +bool DoxWriter::isAnchor(const QString& text) +{ + return anchors.contains(text); +} + +/*! + Write the set of anchors to a file, one per line. + */ +void DoxWriter::writeAnchors() +{ + QFile file("anchors.txt"); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning("Unable to open anchors.txt for writing."); + return; + } + + QTextStream out(&file); + QSet<QString>::const_iterator i = anchors.constBegin(); + while (i != anchors.constEnd()) { + out << *i << "\n"; + ++i; + } + file.close(); +} + +/*! + Read the set of anchors from the anchors file, one per line, + and insert each one into the anchor set. + */ +void DoxWriter::readAnchors() +{ + QFile file("anchors.txt"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning("Unable to open anchors.txt for reading."); + return; + } + + QTextStream in(&file); + while (!in.atEnd()) { + QString line = in.readLine(); + anchors.insert(line); + } + file.close(); +#if 0 + QSet<QString>::const_iterator i = anchors.constBegin(); + while (i != anchors.constEnd()) { + qDebug() << *i; + ++i; + } +#endif +} + +/*! + Inserts \a title into one of the title maps. \a title is + mapped to the \a node name. This function is called during + doxygen pass 1. + */ +void DoxWriter::insertTitle(FakeNode* node, const QString& title) +{ + switch (node->subType()) { + case FakeNode::Example: + if (exampleTitles.contains(title)) { + qWarning() << "DoxWriter::insertTitle():" + << "Duplicate example title:" + << title; + } + else { + exampleTitles[title] = node->name(); + exampleTitlesInverse[node->name()] = title; + } + break; + case FakeNode::HeaderFile: + if (headerFileTitles.contains(title)) { + qWarning() << "DoxWriter::insertTitle():" + << "Duplicate header file title:" + << title; + } + else { + headerFileTitles[title] = node->name(); + headerFileTitlesInverse[node->name()] = title; + } + break; + case FakeNode::File: + if (fileTitles.contains(title)) { + qWarning() << "DoxWriter::insertTitle():" + << "Duplicate file title:" + << title; + } + else { + fileTitles[title] = node->name(); + fileTitlesInverse[node->name()] = title; + } + break; + case FakeNode::Group: + if (groupTitles.contains(title)) { + qWarning() << "DoxWriter::insertTitle():" + << "Duplicate group title:" + << title; + } + else { + groupTitles[title] = node->name(); + groupTitlesInverse[node->name()] = title; + } + break; + case FakeNode::Module: + if (moduleTitles.contains(title)) { + qWarning() << "DoxWriter::insertTitle():" + << "Duplicate module title:" + << title; + } + else { + moduleTitles[title] = node->name(); + moduleTitlesInverse[node->name()] = title; + } + break; + case FakeNode::Page: + if (pageTitles.contains(title)) { + qWarning() << "DoxWriter::insertTitle():" + << "Duplicate page title:" + << title; + } + else { + pageTitles[title] = node->name(); + pageTitlesInverse[node->name()] = title; + } + break; + case FakeNode::ExternalPage: + if (externalPageTitles.contains(title)) { + qWarning() << "DoxWriter::insertTitle():" + << "Duplicate external page title:" + << title; + } + else { + externalPageTitles[title] = node->name(); + externalPageTitlesInverse[node->name()] = title; + } + break; + default: + break; + } +} + +/*! + */ +const QString* DoxWriter::getPageFile(const QString& title) +{ + QStringMapEntry entry = pageTitles.find(title); + return (entry == pageTitles.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getExamplePath(const QString& title) +{ + QStringMapEntry entry = exampleTitles.find(title); + return (entry == exampleTitles.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getFile(const QString& title) +{ + QStringMapEntry entry = fileTitles.find(title); + return (entry == fileTitles.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getHeaderFile(const QString& title) +{ + QStringMapEntry entry = headerFileTitles.find(title); + return (entry == headerFileTitles.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getGroup(const QString& title) +{ + QStringMapEntry entry = groupTitles.find(title); + return (entry == groupTitles.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getModule(const QString& title) +{ + QStringMapEntry entry = moduleTitles.find(title); + return (entry == moduleTitles.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getExternalPage(const QString& title) +{ + QStringMapEntry entry = externalPageTitles.find(title); + return (entry == externalPageTitles.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getPageTitle(const QString& text) +{ + QStringMapEntry entry = pageTitlesInverse.find(text); + return (entry == pageTitlesInverse.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getExampleTitle(const QString& text) +{ + QStringMapEntry entry = exampleTitlesInverse.find(text); + return (entry == exampleTitlesInverse.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getFileTitle(const QString& text) +{ + QStringMapEntry entry = fileTitlesInverse.find(text); + return (entry == fileTitlesInverse.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getHeaderFileTitle(const QString& text) +{ + QStringMapEntry entry = headerFileTitlesInverse.find(text); + return (entry == headerFileTitlesInverse.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getGroupTitle(const QString& text) +{ + QStringMapEntry entry = groupTitlesInverse.find(text); + return (entry == groupTitlesInverse.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getModuleTitle(const QString& text) +{ + QStringMapEntry entry = moduleTitlesInverse.find(text); + return (entry == moduleTitlesInverse.end()) ? 0 : &entry.value(); +} + +/*! + */ +const QString* DoxWriter::getExternalPageTitle(const QString& text) +{ + QStringMapEntry entry = externalPageTitlesInverse.find(text); + return (entry == externalPageTitlesInverse.end()) ? 0 : &entry.value(); +} + +/*! + Serialize \a map to file \a name. + */ +void DoxWriter::writeMap(const QStringMap& map, const QString& name) +{ + + QFile file(name); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning() << "Unable to open" << name << "for writing."; + return; + } + + QTextStream out(&file); + QStringMap::const_iterator i = map.constBegin(); + while (i != map.constEnd()) { + out << i.key() << "\n"; + out << i.value() << "\n"; + ++i; + } + file.close(); +} + +/*! + Read file \a name into the \a map. + */ +void DoxWriter::readMap(QStringMap& map, QStringMap& inverseMap, const QString& name) +{ + QFile file(name); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "Unable to open" << name << "for reading."; + return; + } + + QTextStream in(&file); + while (!in.atEnd()) { + QString title = in.readLine(); + QString value = in.readLine(); + map[title] = value; + inverseMap[value] = title; + } + file.close(); +} + +/*! + Write the sets of titles to text files, one per line. + */ +void DoxWriter::writeTitles() +{ + if (!pageTitles.isEmpty()) + writeMap(pageTitles,"pagetitles.txt"); + if (!fileTitles.isEmpty()) + writeMap(fileTitles,"filetitles.txt"); + if (!headerFileTitles.isEmpty()) + writeMap(headerFileTitles,"headerfiletitles.txt"); + if (!exampleTitles.isEmpty()) + writeMap(exampleTitles,"exampletitles.txt"); + if (!moduleTitles.isEmpty()) + writeMap(moduleTitles,"moduletitles.txt"); + if (!groupTitles.isEmpty()) + writeMap(groupTitles,"grouptitles.txt"); + if (!externalPageTitles.isEmpty()) + writeMap(externalPageTitles,"externalpagetitles.txt"); +} + +/*! + Read the sets of titles from the titles files, one per line, + and insert each one into the appropriate title set. + */ +void DoxWriter::readTitles() +{ + readMap(pageTitles,pageTitlesInverse,"pagetitles.txt"); + readMap(fileTitles,fileTitlesInverse,"filetitles.txt"); + readMap(headerFileTitles,headerFileTitlesInverse,"headerfiletitles.txt"); + readMap(exampleTitles,exampleTitlesInverse,"exampletitles.txt"); + readMap(moduleTitles,moduleTitlesInverse,"moduletitles.txt"); + readMap(groupTitles,groupTitlesInverse,"grouptitles.txt"); + readMap(externalPageTitles, + externalPageTitlesInverse, + "externalpagetitles.txt"); +} + +/*! + Serialize \a map to file \a name. + */ +void DoxWriter::writeMultiMap(const QStringMultiMap& map, const QString& name) +{ + + QFile file(name); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning() << "Unable to open" << name << "for writing."; + return; + } + + QTextStream out(&file); + QStringMultiMap::const_iterator i = map.constBegin(); + while (i != map.constEnd()) { + out << i.key() << "\n"; + out << i.value() << "\n"; + ++i; + } + file.close(); +} + +/*! + Write the4 property names and variable names to text files. + */ +void DoxWriter::writeMembers() +{ + if (!variables.isEmpty()) + writeMultiMap(variables,"variables.txt"); + if (!properties.isEmpty()) + writeMultiMap(properties,"properties.txt"); + if (!enums.isEmpty()) + writeMultiMap(enums,"enums.txt"); +} + +/*! + Read file \a name into the \a map. + */ +void DoxWriter::readMultiMap(QStringMultiMap& map, const QString& name) +{ + QFile file(name); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "Unable to open" << name << "for reading."; + return; + } + + QTextStream in(&file); + while (!in.atEnd()) { + QString member = in.readLine(); + QString className = in.readLine(); + map.insert(member,className); + } + file.close(); +} + +/*! + Read the property names and variable names from the test files. + */ +void DoxWriter::readMembers() +{ + readMultiMap(variables,"variables.txt"); + readMultiMap(properties,"properties.txt"); + readMultiMap(enums,"enums.txt"); +} + +/*! + Return true if \a name is a property. Loads \a classes with + the names of all the classes in which \a name is a property. + */ +bool DoxWriter::isProperty(const QString& name, QStringList& classes) +{ + classes = properties.values(name); + return !classes.isEmpty(); +} + +/*! + Return true if \a name is a variable. Loads \a classes with + the names of all the classes in which \a name is a variable. + */ +bool DoxWriter::isVariable(const QString& name, QStringList& classes) +{ + classes = variables.values(name); + return !classes.isEmpty(); +} + +/*! + Return true if \a name is an enum type. Loads \a classes with + the names of all the classes in which \a name is an enum type. + */ +bool DoxWriter::isEnum(const QString& name, QStringList& classes) +{ + classes = enums.values(name); + return !classes.isEmpty(); +} +#endif + +QT_END_NAMESPACE |