| /**************************************************************************** |
| ** |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the tools applications of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "doc.h" |
| |
| #include "atom.h" |
| #include "config.h" |
| #include "codemarker.h" |
| #include "editdistance.h" |
| #include "generator.h" |
| #include "loggingcategory.h" |
| #include "openedlist.h" |
| #include "quoter.h" |
| #include "text.h" |
| #include "tokenizer.h" |
| |
| #include <QtCore/qdatetime.h> |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qfile.h> |
| #include <QtCore/qfileinfo.h> |
| #include <QtCore/qhash.h> |
| #include <QtCore/qregexp.h> |
| #include <QtCore/qtextstream.h> |
| |
| #include <ctype.h> |
| #include <limits.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_GLOBAL_STATIC(QSet<QString>, null_Set_QString) |
| Q_GLOBAL_STATIC(TopicList, nullTopicList) |
| Q_GLOBAL_STATIC(QStringList, null_QStringList) |
| Q_GLOBAL_STATIC(QVector<Text>, null_QVector_Text) |
| Q_GLOBAL_STATIC(QStringMultiMap, null_QStringMultiMap) |
| |
| struct Macro |
| { |
| QString defaultDef; |
| Location defaultDefLocation; |
| QStringMap otherDefs; |
| int numParams; |
| }; |
| |
| enum { |
| CMD_A, |
| CMD_ANNOTATEDLIST, |
| CMD_B, |
| CMD_BADCODE, |
| CMD_BOLD, |
| CMD_BR, |
| CMD_BRIEF, |
| CMD_C, |
| CMD_CAPTION, |
| CMD_CODE, |
| CMD_CODELINE, |
| CMD_DIV, |
| CMD_DOTS, |
| CMD_E, |
| CMD_ELSE, |
| CMD_ENDCODE, |
| CMD_ENDDIV, |
| CMD_ENDFOOTNOTE, |
| CMD_ENDIF, |
| CMD_ENDLEGALESE, |
| CMD_ENDLINK, |
| CMD_ENDLIST, |
| CMD_ENDMAPREF, |
| CMD_ENDOMIT, |
| CMD_ENDQUOTATION, |
| CMD_ENDRAW, |
| CMD_ENDSECTION1, |
| CMD_ENDSECTION2, |
| CMD_ENDSECTION3, |
| CMD_ENDSECTION4, |
| CMD_ENDSIDEBAR, |
| CMD_ENDTABLE, |
| CMD_ENDTOPICREF, |
| CMD_FOOTNOTE, |
| CMD_GENERATELIST, |
| CMD_GRANULARITY, |
| CMD_HEADER, |
| CMD_HR, |
| CMD_I, |
| CMD_IF, |
| CMD_IMAGE, |
| CMD_IMPORTANT, |
| CMD_INCLUDE, |
| CMD_INLINEIMAGE, |
| CMD_INDEX, |
| CMD_INPUT, |
| CMD_KEYWORD, |
| CMD_L, |
| CMD_LEGALESE, |
| CMD_LI, |
| CMD_LINK, |
| CMD_LIST, |
| CMD_MAPREF, |
| CMD_META, |
| CMD_NEWCODE, |
| CMD_NOTE, |
| CMD_O, |
| CMD_OLDCODE, |
| CMD_OMIT, |
| CMD_OMITVALUE, |
| CMD_OVERLOAD, |
| 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_SINCELIST, |
| CMD_SKIPLINE, |
| CMD_SKIPTO, |
| CMD_SKIPUNTIL, |
| CMD_SNIPPET, |
| CMD_SPAN, |
| CMD_SUB, |
| CMD_SUP, |
| CMD_TABLE, |
| CMD_TABLEOFCONTENTS, |
| CMD_TARGET, |
| CMD_TOPICREF, |
| CMD_TT, |
| CMD_UICONTROL, |
| CMD_UNDERLINE, |
| CMD_UNICODE, |
| CMD_VALUE, |
| CMD_WARNING, |
| CMD_QML, |
| CMD_ENDQML, |
| CMD_CPP, |
| CMD_ENDCPP, |
| CMD_QMLTEXT, |
| CMD_ENDQMLTEXT, |
| CMD_CPPTEXT, |
| CMD_ENDCPPTEXT, |
| CMD_JS, |
| CMD_ENDJS, |
| NOT_A_CMD |
| }; |
| |
| static struct |
| { |
| const char *english; |
| int no; |
| QString *alias; |
| } cmds[] = { { "a", CMD_A, nullptr }, |
| { "annotatedlist", CMD_ANNOTATEDLIST, nullptr }, |
| { "b", CMD_B, nullptr }, |
| { "badcode", CMD_BADCODE, nullptr }, |
| { "bold", CMD_BOLD, nullptr }, |
| { "br", CMD_BR, nullptr }, |
| { "brief", CMD_BRIEF, nullptr }, |
| { "c", CMD_C, nullptr }, |
| { "caption", CMD_CAPTION, nullptr }, |
| { "code", CMD_CODE, nullptr }, |
| { "codeline", CMD_CODELINE, nullptr }, |
| { "div", CMD_DIV, nullptr }, |
| { "dots", CMD_DOTS, nullptr }, |
| { "e", CMD_E, nullptr }, |
| { "else", CMD_ELSE, nullptr }, |
| { "endcode", CMD_ENDCODE, nullptr }, |
| { "enddiv", CMD_ENDDIV, nullptr }, |
| { "endfootnote", CMD_ENDFOOTNOTE, nullptr }, |
| { "endif", CMD_ENDIF, nullptr }, |
| { "endlegalese", CMD_ENDLEGALESE, nullptr }, |
| { "endlink", CMD_ENDLINK, nullptr }, |
| { "endlist", CMD_ENDLIST, nullptr }, |
| { "endmapref", CMD_ENDMAPREF, nullptr }, |
| { "endomit", CMD_ENDOMIT, nullptr }, |
| { "endquotation", CMD_ENDQUOTATION, nullptr }, |
| { "endraw", CMD_ENDRAW, nullptr }, |
| { "endsection1", CMD_ENDSECTION1, nullptr }, // ### don't document for now |
| { "endsection2", CMD_ENDSECTION2, nullptr }, // ### don't document for now |
| { "endsection3", CMD_ENDSECTION3, nullptr }, // ### don't document for now |
| { "endsection4", CMD_ENDSECTION4, nullptr }, // ### don't document for now |
| { "endsidebar", CMD_ENDSIDEBAR, nullptr }, |
| { "endtable", CMD_ENDTABLE, nullptr }, |
| { "endtopicref", CMD_ENDTOPICREF, nullptr }, |
| { "footnote", CMD_FOOTNOTE, nullptr }, |
| { "generatelist", CMD_GENERATELIST, nullptr }, |
| { "granularity", CMD_GRANULARITY, nullptr }, // ### don't document for now |
| { "header", CMD_HEADER, nullptr }, |
| { "hr", CMD_HR, nullptr }, |
| { "i", CMD_I, nullptr }, |
| { "if", CMD_IF, nullptr }, |
| { "image", CMD_IMAGE, nullptr }, |
| { "important", CMD_IMPORTANT, nullptr }, |
| { "include", CMD_INCLUDE, nullptr }, |
| { "inlineimage", CMD_INLINEIMAGE, nullptr }, |
| { "index", CMD_INDEX, nullptr }, // ### don't document for now |
| { "input", CMD_INPUT, nullptr }, |
| { "keyword", CMD_KEYWORD, nullptr }, |
| { "l", CMD_L, nullptr }, |
| { "legalese", CMD_LEGALESE, nullptr }, |
| { "li", CMD_LI, nullptr }, |
| { "link", CMD_LINK, nullptr }, |
| { "list", CMD_LIST, nullptr }, |
| { "mapref", CMD_MAPREF, nullptr }, |
| { "meta", CMD_META, nullptr }, |
| { "newcode", CMD_NEWCODE, nullptr }, |
| { "note", CMD_NOTE, nullptr }, |
| { "o", CMD_O, nullptr }, |
| { "oldcode", CMD_OLDCODE, nullptr }, |
| { "omit", CMD_OMIT, nullptr }, |
| { "omitvalue", CMD_OMITVALUE, nullptr }, |
| { "overload", CMD_OVERLOAD, nullptr }, |
| { "printline", CMD_PRINTLINE, nullptr }, |
| { "printto", CMD_PRINTTO, nullptr }, |
| { "printuntil", CMD_PRINTUNTIL, nullptr }, |
| { "quotation", CMD_QUOTATION, nullptr }, |
| { "quotefile", CMD_QUOTEFILE, nullptr }, |
| { "quotefromfile", CMD_QUOTEFROMFILE, nullptr }, |
| { "quotefunction", CMD_QUOTEFUNCTION, nullptr }, |
| { "raw", CMD_RAW, nullptr }, |
| { "row", CMD_ROW, nullptr }, |
| { "sa", CMD_SA, nullptr }, |
| { "section1", CMD_SECTION1, nullptr }, |
| { "section2", CMD_SECTION2, nullptr }, |
| { "section3", CMD_SECTION3, nullptr }, |
| { "section4", CMD_SECTION4, nullptr }, |
| { "sidebar", CMD_SIDEBAR, nullptr }, |
| { "sincelist", CMD_SINCELIST, nullptr }, |
| { "skipline", CMD_SKIPLINE, nullptr }, |
| { "skipto", CMD_SKIPTO, nullptr }, |
| { "skipuntil", CMD_SKIPUNTIL, nullptr }, |
| { "snippet", CMD_SNIPPET, nullptr }, |
| { "span", CMD_SPAN, nullptr }, |
| { "sub", CMD_SUB, nullptr }, |
| { "sup", CMD_SUP, nullptr }, |
| { "table", CMD_TABLE, nullptr }, |
| { "tableofcontents", CMD_TABLEOFCONTENTS, nullptr }, |
| { "target", CMD_TARGET, nullptr }, |
| { "topicref", CMD_TOPICREF, nullptr }, |
| { "tt", CMD_TT, nullptr }, |
| { "uicontrol", CMD_UICONTROL, nullptr }, |
| { "underline", CMD_UNDERLINE, nullptr }, |
| { "unicode", CMD_UNICODE, nullptr }, |
| { "value", CMD_VALUE, nullptr }, |
| { "warning", CMD_WARNING, nullptr }, |
| { "qml", CMD_QML, nullptr }, |
| { "endqml", CMD_ENDQML, nullptr }, |
| { "cpp", CMD_CPP, nullptr }, |
| { "endcpp", CMD_ENDCPP, nullptr }, |
| { "qmltext", CMD_QMLTEXT, nullptr }, |
| { "endqmltext", CMD_ENDQMLTEXT, nullptr }, |
| { "cpptext", CMD_CPPTEXT, nullptr }, |
| { "endcpptext", CMD_ENDCPPTEXT, nullptr }, |
| { "js", CMD_JS, nullptr }, |
| { "endjs", CMD_ENDJS, nullptr }, |
| { nullptr, 0, nullptr } }; |
| |
| 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: |
| Doc::Sections granularity_; |
| Doc::Sections section_; // ### |
| QVector<Atom *> tableOfContents_; |
| QVector<int> tableOfContentsLevels_; |
| QVector<Atom *> keywords_; |
| QVector<Atom *> targets_; |
| QStringMultiMap metaMap_; |
| |
| DocPrivateExtra() : granularity_(Doc::Part), section_(Doc::NoSection) {} |
| }; |
| |
| 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(); |
| } |
| |
| typedef QMap<QString, ArgList> CommandMap; |
| |
| class DocPrivate : public Shared |
| { |
| public: |
| DocPrivate(const Location &start = Location(), const Location &end = Location(), |
| const QString &source = QString()); |
| ~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; |
| QVector<Text> alsoList; |
| QStringList enumItemList; |
| QStringList omitEnumItemList; |
| QSet<QString> metacommandsUsed; |
| CommandMap metaCommandMap; |
| bool hasLegalese : 1; |
| bool hasSectioningUnits : 1; |
| DocPrivateExtra *extra; |
| TopicList topics_; |
| DitaRefList ditamap_; |
| }; |
| |
| DocPrivate::DocPrivate(const Location &start, const Location &end, const QString &source) |
| : start_loc(start), |
| end_loc(end), |
| src(source), |
| hasLegalese(false), |
| hasSectioningUnits(false), |
| extra(nullptr) |
| { |
| // nothing. |
| } |
| |
| /*! |
| If the doc is a ditamap, the destructor deletes each element |
| in the ditamap structure. These were allocated as needed. |
| */ |
| DocPrivate::~DocPrivate() |
| { |
| delete extra; |
| qDeleteAll(ditamap_); |
| } |
| |
| void DocPrivate::addAlso(const Text &also) |
| { |
| alsoList.append(also); |
| } |
| |
| void DocPrivate::constructExtra() |
| { |
| if (extra == nullptr) |
| 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(QLatin1Char(':')); |
| } 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 |
| { |
| Q_DECLARE_TR_FUNCTIONS(QDoc::DocParser) |
| |
| public: |
| void parse(const QString &source, DocPrivate *docPrivate, const QSet<QString> &metaCommandSet, |
| const QSet<QString> &possibleTopics); |
| |
| 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 QStringList ignorewords; |
| static bool quoting; |
| |
| private: |
| Location &location(); |
| QString detailsUnknownCommand(const QSet<QString> &metaCommandSet, const QString &str); |
| void insertTarget(const QString &target, bool keyword); |
| void include(const QString &fileName, const QString &identifier); |
| void startFormat(const QString &format, int cmd); |
| bool openCommand(int cmd); |
| bool closeCommand(int endCmd); |
| void startSection(Doc::Sections unit, int cmd); |
| void endSection(int unit, int endCmd); |
| void parseAlso(); |
| void append(const QString &string); |
| void append(Atom::AtomType type, const QString &string = QString()); |
| void append(Atom::AtomType type, const QString &p1, const QString &p2); |
| void append(const QString &p1, const QString &p2); |
| void appendChar(QChar ch); |
| void appendWord(const QString &word); |
| void appendToCode(const QString &code); |
| void appendToCode(const QString &code, Atom::AtomType defaultType); |
| void startNewPara(); |
| void enterPara(Atom::AtomType leftType = Atom::ParaLeft, |
| Atom::AtomType rightType = Atom::ParaRight, const QString &string = QString()); |
| void leavePara(); |
| void leaveValue(); |
| void leaveValueList(); |
| void leaveTableRow(); |
| CodeMarker *quoteFromFile(); |
| bool expandMacro(); |
| void expandMacro(const QString &name, const QString &def, int numParams); |
| QString expandMacroToString(const QString &name, const QString &def, int numParams, |
| const QString &matchExpr); |
| Doc::Sections getSectioningUnit(); |
| QString getArgument(bool verbatim = false); |
| QString getBracedArgument(bool verbatim); |
| QString getBracketedArgument(); |
| QString getOptionalArgument(); |
| QString getRestOfLine(); |
| QString getMetaCommandArgument(const QString &cmdStr); |
| QString getUntilEnd(int cmd); |
| QString getCode(int cmd, CodeMarker *marker, const QString &argStr = QString()); |
| QString getUnmarkedCode(int cmd); |
| |
| inline bool isAutoLinkString(const QString &word); |
| bool isAutoLinkString(const QString &word, int &curPos); |
| bool isBlankLine(); |
| bool isLeftBraceAhead(); |
| bool isLeftBracketAhead(); |
| void skipSpacesOnLine(); |
| void skipSpacesOrOneEndl(); |
| void skipAllSpaces(); |
| void skipToNextPreprocessorCommand(); |
| static bool isCode(const Atom *atom); |
| static bool isQuote(const Atom *atom); |
| |
| QStack<int> openedInputs; |
| |
| QString input_; |
| int pos; |
| int backslashPos; |
| int endPos; |
| int len; |
| Location cachedLoc; |
| int cachedPos; |
| |
| DocPrivate *priv; |
| enum ParagraphState { OutsideParagraph, InSingleLineParagraph, InMultiLineParagraph }; |
| ParagraphState paraState; |
| bool inTableHeader; |
| bool inTableRow; |
| bool inTableItem; |
| bool indexStartedPara; // ### rename |
| Atom::AtomType pendingParaLeftType; |
| Atom::AtomType pendingParaRightType; |
| QString pendingParaString; |
| |
| int braceDepth; |
| Doc::Sections currentSection; |
| QMap<QString, Location> targetMap_; |
| QMap<int, QString> pendingFormats; |
| QStack<int> openedCommands; |
| QStack<OpenedList> openedLists; |
| Quoter quoter; |
| QStack<DitaRef *> ditarefs_; |
| Atom *lastAtom; |
| }; |
| |
| int DocParser::tabSize; |
| QStringList DocParser::exampleFiles; |
| QStringList DocParser::exampleDirs; |
| QStringList DocParser::sourceFiles; |
| QStringList DocParser::sourceDirs; |
| QStringList DocParser::ignorewords; |
| bool DocParser::quoting = false; |
| |
| /*! |
| 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, const QSet<QString> &possibleTopics) |
| { |
| input_ = source; |
| pos = 0; |
| len = input_.length(); |
| cachedLoc = docPrivate->start_loc; |
| cachedPos = 0; |
| priv = docPrivate; |
| priv->text << Atom::Nop; |
| priv->topics_.clear(); |
| |
| paraState = OutsideParagraph; |
| inTableHeader = false; |
| inTableRow = false; |
| inTableItem = false; |
| indexStartedPara = false; |
| pendingParaLeftType = Atom::Nop; |
| pendingParaRightType = Atom::Nop; |
| |
| braceDepth = 0; |
| currentSection = Doc::NoSection; |
| openedCommands.push(CMD_OMIT); |
| quoter.reset(); |
| |
| CodeMarker *marker = nullptr; |
| Atom *currentLinkAtom = nullptr; |
| QString p1, p2; |
| QStack<bool> preprocessorSkipping; |
| int numPreprocessorSkipping = 0; |
| |
| while (pos < len) { |
| QChar ch = input_.at(pos); |
| |
| switch (ch.unicode()) { |
| case '\\': { |
| QString cmdStr; |
| backslashPos = pos; |
| ++pos; |
| while (pos < len) { |
| ch = input_.at(pos); |
| if (ch.isLetterOrNumber()) { |
| cmdStr += ch; |
| ++pos; |
| } else { |
| break; |
| } |
| } |
| endPos = pos; |
| if (cmdStr.isEmpty()) { |
| if (pos < len) { |
| enterPara(); |
| if (input_.at(pos).isSpace()) { |
| skipAllSpaces(); |
| appendChar(QLatin1Char(' ')); |
| } else { |
| appendChar(input_.at(pos++)); |
| } |
| } |
| } else { |
| // Ignore quoting atoms to make appendToCode() |
| // append to the correct atom. |
| if (!quoting || !isQuote(priv->text.lastAtom())) |
| lastAtom = priv->text.lastAtom(); |
| |
| int cmd = cmdHash()->value(cmdStr, NOT_A_CMD); |
| switch (cmd) { |
| case CMD_A: |
| enterPara(); |
| p1 = getArgument(); |
| append(Atom::FormattingLeft, ATOM_FORMATTING_PARAMETER); |
| append(Atom::String, p1); |
| append(Atom::FormattingRight, ATOM_FORMATTING_PARAMETER); |
| priv->params.insert(p1); |
| break; |
| case CMD_BADCODE: |
| leavePara(); |
| append(Atom::CodeBad, |
| getCode(CMD_BADCODE, marker, getMetaCommandArgument(cmdStr))); |
| break; |
| case CMD_BR: |
| enterPara(); |
| append(Atom::BR); |
| break; |
| case CMD_BOLD: |
| location().warning(tr("'\\bold' is deprecated. Use '\\b'")); |
| Q_FALLTHROUGH(); |
| case CMD_B: |
| startFormat(ATOM_FORMATTING_BOLD, cmd); |
| break; |
| case CMD_BRIEF: |
| leavePara(); |
| enterPara(Atom::BriefLeft, Atom::BriefRight); |
| break; |
| case CMD_C: |
| enterPara(); |
| p1 = untabifyEtc(getArgument(true)); |
| marker = CodeMarker::markerForCode(p1); |
| append(Atom::C, marker->markedUpCode(p1, nullptr, location())); |
| break; |
| case CMD_CAPTION: |
| leavePara(); |
| enterPara(Atom::CaptionLeft, Atom::CaptionRight); |
| break; |
| case CMD_CODE: |
| leavePara(); |
| append(Atom::Code, getCode(CMD_CODE, nullptr, getMetaCommandArgument(cmdStr))); |
| break; |
| case CMD_QML: |
| leavePara(); |
| append(Atom::Qml, |
| getCode(CMD_QML, CodeMarker::markerForLanguage(QLatin1String("QML")), |
| getMetaCommandArgument(cmdStr))); |
| break; |
| case CMD_QMLTEXT: |
| append(Atom::QmlText); |
| break; |
| case CMD_JS: |
| leavePara(); |
| append(Atom::JavaScript, |
| getCode(CMD_JS, |
| CodeMarker::markerForLanguage(QLatin1String("JavaScript")), |
| getMetaCommandArgument(cmdStr))); |
| break; |
| case CMD_DIV: |
| leavePara(); |
| p1 = getArgument(true); |
| append(Atom::DivLeft, p1); |
| openedCommands.push(cmd); |
| break; |
| case CMD_ENDDIV: |
| leavePara(); |
| append(Atom::DivRight); |
| closeCommand(cmd); |
| break; |
| case CMD_CODELINE: |
| if (quoting) { |
| append(Atom::CodeQuoteCommand, cmdStr); |
| append(Atom::CodeQuoteArgument, " "); |
| } |
| if (isCode(lastAtom) && lastAtom->string().endsWith("\n\n")) |
| lastAtom->chopString(); |
| appendToCode("\n"); |
| break; |
| case CMD_DOTS: { |
| QString arg = getOptionalArgument(); |
| if (arg.isEmpty()) |
| arg = "4"; |
| if (quoting) { |
| append(Atom::CodeQuoteCommand, cmdStr); |
| append(Atom::CodeQuoteArgument, arg); |
| } |
| if (isCode(lastAtom) && lastAtom->string().endsWith("\n\n")) |
| lastAtom->chopString(); |
| |
| int indent = arg.toInt(); |
| for (int i = 0; i < indent; ++i) |
| appendToCode(" "); |
| appendToCode("...\n"); |
| 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_ENDCODE: |
| closeCommand(cmd); |
| break; |
| case CMD_ENDQML: |
| closeCommand(cmd); |
| break; |
| case CMD_ENDQMLTEXT: |
| append(Atom::EndQmlText); |
| break; |
| case CMD_ENDJS: |
| closeCommand(cmd); |
| break; |
| case CMD_ENDFOOTNOTE: |
| if (closeCommand(cmd)) { |
| leavePara(); |
| append(Atom::FootnoteRight); |
| paraState = InMultiLineParagraph; // ### |
| } |
| 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(QLatin1Char(' '))) |
| 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_ENDMAPREF: |
| case CMD_ENDTOPICREF: |
| if (closeCommand(cmd)) |
| ditarefs_.pop(); |
| break; |
| case CMD_ENDOMIT: |
| closeCommand(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(Doc::Section1, cmd); |
| break; |
| case CMD_ENDSECTION2: |
| endSection(Doc::Section2, cmd); |
| break; |
| case CMD_ENDSECTION3: |
| endSection(Doc::Section3, cmd); |
| break; |
| case CMD_ENDSECTION4: |
| endSection(Doc::Section4, 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_FOOTNOTE: |
| if (openCommand(cmd)) { |
| enterPara(); |
| append(Atom::FootnoteLeft); |
| paraState = OutsideParagraph; |
| } |
| break; |
| case CMD_ANNOTATEDLIST: |
| append(Atom::AnnotatedList, getArgument()); |
| break; |
| case CMD_SINCELIST: |
| append(Atom::SinceList, getRestOfLine().simplified()); |
| break; |
| case CMD_GENERATELIST: { |
| QString arg1 = getArgument(); |
| QString arg2 = getOptionalArgument(); |
| if (!arg2.isEmpty()) |
| arg1 += " " + arg2; |
| append(Atom::GeneratedList, arg1); |
| } 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: |
| location().warning(tr( |
| "'\\i' is deprecated. Use '\\e' for italic or '\\li' for list item")); |
| Q_FALLTHROUGH(); |
| case CMD_E: |
| startFormat(ATOM_FORMATTING_ITALIC, cmd); |
| break; |
| case CMD_HR: |
| leavePara(); |
| append(Atom::HR); |
| 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_IMPORTANT: |
| leavePara(); |
| enterPara(Atom::ImportantLeft, Atom::ImportantRight); |
| break; |
| case CMD_INCLUDE: |
| case CMD_INPUT: { |
| QString fileName = getArgument(); |
| QString identifier = getRestOfLine(); |
| include(fileName, identifier); |
| break; |
| } |
| case CMD_INLINEIMAGE: |
| enterPara(); |
| append(Atom::InlineImage, getArgument()); |
| append(Atom::ImageText, getRestOfLine()); |
| append(Atom::String, " "); |
| break; |
| case CMD_INDEX: |
| if (paraState == OutsideParagraph) { |
| 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 (isLeftBracketAhead()) |
| p2 = getBracketedArgument(); |
| if (isLeftBraceAhead()) { |
| p1 = getArgument(); |
| append(p1, p2); |
| if (!p2.isEmpty() && !(priv->text.lastAtom()->error().isEmpty())) |
| location().warning( |
| tr("Check parameter in '[ ]' of '\\l' command: '%1', " |
| "possible misspelling, or unrecognized module name") |
| .arg(priv->text.lastAtom()->error())); |
| if (isLeftBraceAhead()) { |
| currentLinkAtom = priv->text.lastAtom(); |
| startFormat(ATOM_FORMATTING_LINK, cmd); |
| } else { |
| append(Atom::FormattingLeft, ATOM_FORMATTING_LINK); |
| append(Atom::String, cleanLink(p1)); |
| append(Atom::FormattingRight, ATOM_FORMATTING_LINK); |
| } |
| } else { |
| p1 = getArgument(); |
| append(p1, p2); |
| if (!p2.isEmpty() && !(priv->text.lastAtom()->error().isEmpty())) |
| location().warning( |
| tr("Check parameter in '[ ]' of '\\l' command: '%1', " |
| "possible misspelling, or unrecognized module name") |
| .arg(priv->text.lastAtom()->error())); |
| append(Atom::FormattingLeft, ATOM_FORMATTING_LINK); |
| append(Atom::String, cleanLink(p1)); |
| append(Atom::FormattingRight, ATOM_FORMATTING_LINK); |
| } |
| p2.clear(); |
| break; |
| case CMD_LEGALESE: |
| leavePara(); |
| if (openCommand(cmd)) |
| append(Atom::LegaleseLeft); |
| docPrivate->hasLegalese = true; |
| break; |
| case CMD_LINK: |
| if (openCommand(cmd)) { |
| enterPara(); |
| p1 = getArgument(); |
| append(p1); |
| append(Atom::FormattingLeft, ATOM_FORMATTING_LINK); |
| skipSpacesOrOneEndl(); |
| } |
| break; |
| case CMD_LIST: |
| if (openCommand(cmd)) { |
| leavePara(); |
| openedLists.push(OpenedList(location(), getOptionalArgument())); |
| } |
| break; |
| case CMD_TOPICREF: |
| case CMD_MAPREF: |
| if (openCommand(cmd)) { |
| DitaRef *t = nullptr; |
| if (cmd == CMD_MAPREF) |
| t = new MapRef(); |
| else |
| t = new TopicRef(); |
| t->setNavtitle(getArgument(true)); |
| if (cmd == CMD_MAPREF) |
| t->setHref(getArgument()); |
| else |
| t->setHref(getOptionalArgument()); |
| if (ditarefs_.isEmpty()) |
| priv->ditamap_.append(t); |
| else |
| ditarefs_.top()->appendSubref(t); |
| ditarefs_.push(t); |
| } |
| break; |
| case CMD_META: |
| priv->constructExtra(); |
| p1 = getArgument(); |
| priv->extra->metaMap_.insert(p1, getArgument()); |
| break; |
| case CMD_NEWCODE: |
| location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_NEWCODE))); |
| break; |
| case CMD_NOTE: |
| leavePara(); |
| enterPara(Atom::NoteLeft, Atom::NoteRight); |
| break; |
| case CMD_O: |
| location().warning(tr("'\\o' is deprecated. Use '\\li'")); |
| Q_FALLTHROUGH(); |
| case CMD_LI: |
| 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) { |
| p1 = "1,1"; |
| p2.clear(); |
| if (isLeftBraceAhead()) { |
| p1 = getArgument(); |
| if (isLeftBraceAhead()) |
| p2 = getArgument(); |
| } |
| |
| if (!inTableHeader && !inTableRow) { |
| location().warning(tr("Missing '\\%1' or '\\%2' before '\\%3'") |
| .arg(cmdName(CMD_HEADER)) |
| .arg(cmdName(CMD_ROW)) |
| .arg(cmdName(CMD_LI))); |
| append(Atom::TableRowLeft); |
| inTableRow = true; |
| } else if (inTableItem) { |
| append(Atom::TableItemRight); |
| inTableItem = false; |
| } |
| |
| append(Atom::TableItemLeft, p1, p2); |
| 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(); |
| append(Atom::CodeOld, getCode(CMD_OLDCODE, marker)); |
| append(Atom::CodeNew, getCode(CMD_NEWCODE, marker)); |
| break; |
| case CMD_OMIT: |
| getUntilEnd(cmd); |
| break; |
| case CMD_OMITVALUE: |
| p1 = getArgument(); |
| if (!priv->enumItemList.contains(p1)) |
| priv->enumItemList.append(p1); |
| if (!priv->omitEnumItemList.contains(p1)) |
| priv->omitEnumItemList.append(p1); |
| break; |
| case CMD_PRINTLINE: { |
| leavePara(); |
| QString rest = getRestOfLine(); |
| if (quoting) { |
| append(Atom::CodeQuoteCommand, cmdStr); |
| append(Atom::CodeQuoteArgument, rest); |
| } |
| appendToCode(quoter.quoteLine(location(), cmdStr, rest)); |
| break; |
| } |
| case CMD_PRINTTO: { |
| leavePara(); |
| QString rest = getRestOfLine(); |
| if (quoting) { |
| append(Atom::CodeQuoteCommand, cmdStr); |
| append(Atom::CodeQuoteArgument, rest); |
| } |
| appendToCode(quoter.quoteTo(location(), cmdStr, rest)); |
| break; |
| } |
| case CMD_PRINTUNTIL: { |
| leavePara(); |
| QString rest = getRestOfLine(); |
| if (quoting) { |
| append(Atom::CodeQuoteCommand, cmdStr); |
| append(Atom::CodeQuoteArgument, rest); |
| } |
| appendToCode(quoter.quoteUntil(location(), cmdStr, rest)); |
| 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::CodeQuoteCommand, cmdStr); |
| append(Atom::CodeQuoteArgument, fileName); |
| } |
| append(Atom::Code, quoter.quoteTo(location(), cmdStr, QString())); |
| quoter.reset(); |
| break; |
| } |
| case CMD_QUOTEFROMFILE: { |
| leavePara(); |
| QString arg = getArgument(); |
| if (quoting) { |
| append(Atom::CodeQuoteCommand, cmdStr); |
| append(Atom::CodeQuoteArgument, arg); |
| } |
| Doc::quoteFromFile(location(), quoter, arg); |
| break; |
| } |
| case CMD_QUOTEFUNCTION: { |
| leavePara(); |
| marker = quoteFromFile(); |
| p1 = getRestOfLine(); |
| if (quoting) { |
| append(Atom::CodeQuoteCommand, cmdStr); |
| append(Atom::CodeQuoteArgument, slashed(marker->functionEndRegExp(p1))); |
| } |
| quoter.quoteTo(location(), cmdStr, slashed(marker->functionBeginRegExp(p1))); |
| append(Atom::Code, |
| quoter.quoteUntil(location(), cmdStr, |
| slashed(marker->functionEndRegExp(p1)))); |
| quoter.reset(); |
| break; |
| } |
| case CMD_RAW: |
| leavePara(); |
| p1 = getRestOfLine(); |
| if (p1.isEmpty()) |
| location().warning( |
| tr("Missing format name after '\\%1'").arg(cmdName(CMD_RAW))); |
| append(Atom::FormatIf, p1); |
| append(Atom::RawString, untabifyEtc(getUntilEnd(cmd))); |
| append(Atom::FormatElse); |
| append(Atom::FormatEndif); |
| break; |
| case CMD_ROW: |
| if (openedCommands.top() == CMD_TABLE) { |
| p1.clear(); |
| if (isLeftBraceAhead()) |
| p1 = getArgument(true); |
| leaveTableRow(); |
| append(Atom::TableRowLeft, p1); |
| 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(); |
| QString rest = getRestOfLine(); |
| if (quoting) { |
| append(Atom::CodeQuoteCommand, cmdStr); |
| append(Atom::CodeQuoteArgument, rest); |
| } |
| quoter.quoteLine(location(), cmdStr, rest); |
| break; |
| } |
| case CMD_SKIPTO: { |
| leavePara(); |
| QString rest = getRestOfLine(); |
| if (quoting) { |
| append(Atom::CodeQuoteCommand, cmdStr); |
| append(Atom::CodeQuoteArgument, rest); |
| } |
| quoter.quoteTo(location(), cmdStr, rest); |
| break; |
| } |
| case CMD_SKIPUNTIL: { |
| leavePara(); |
| QString rest = getRestOfLine(); |
| if (quoting) { |
| append(Atom::CodeQuoteCommand, cmdStr); |
| append(Atom::CodeQuoteArgument, rest); |
| } |
| quoter.quoteUntil(location(), cmdStr, rest); |
| break; |
| } |
| case CMD_SPAN: |
| p1 = ATOM_FORMATTING_SPAN + getArgument(true); |
| startFormat(p1, cmd); |
| break; |
| case CMD_SNIPPET: { |
| leavePara(); |
| QString snippet = getArgument(); |
| QString identifier = getRestOfLine(); |
| if (quoting) { |
| append(Atom::SnippetCommand, cmdStr); |
| append(Atom::SnippetLocation, snippet); |
| append(Atom::SnippetIdentifier, identifier); |
| } |
| marker = Doc::quoteFromFile(location(), quoter, snippet); |
| appendToCode(quoter.quoteSnippet(location(), identifier), marker->atomType()); |
| break; |
| } |
| case CMD_SUB: |
| startFormat(ATOM_FORMATTING_SUBSCRIPT, cmd); |
| break; |
| case CMD_SUP: |
| startFormat(ATOM_FORMATTING_SUPERSCRIPT, cmd); |
| break; |
| case CMD_TABLE: |
| p1 = getOptionalArgument(); |
| p2 = getOptionalArgument(); |
| if (openCommand(cmd)) { |
| leavePara(); |
| append(Atom::TableLeft, p1, p2); |
| inTableHeader = false; |
| inTableRow = false; |
| inTableItem = false; |
| } |
| break; |
| case CMD_TABLEOFCONTENTS: |
| p1 = "1"; |
| if (isLeftBraceAhead()) |
| p1 = getArgument(); |
| p1 += QLatin1Char(','); |
| p1 += QString::number((int)getSectioningUnit()); |
| append(Atom::TableOfContents, p1); |
| break; |
| case CMD_TARGET: |
| insertTarget(getRestOfLine(), false); |
| break; |
| case CMD_TT: |
| startFormat(ATOM_FORMATTING_TELETYPE, cmd); |
| break; |
| case CMD_UICONTROL: |
| startFormat(ATOM_FORMATTING_UICONTROL, cmd); |
| break; |
| case CMD_UNDERLINE: |
| startFormat(ATOM_FORMATTING_UNDERLINE, cmd); |
| break; |
| case CMD_UNICODE: { |
| enterPara(); |
| p1 = getArgument(); |
| bool ok; |
| uint unicodeChar = p1.toUInt(&ok, 0); |
| if (!ok || (unicodeChar == 0x0000) || (unicodeChar > 0xFFFE)) |
| location().warning(tr("Invalid Unicode character '%1' specified with '%2'") |
| .arg(p1, cmdName(CMD_UNICODE))); |
| else |
| append(Atom::String, QChar(unicodeChar)); |
| break; |
| } |
| case CMD_VALUE: |
| leaveValue(); |
| if (openedLists.top().style() == OpenedList::Value) { |
| QString p2; |
| p1 = getArgument(); |
| if (p1.startsWith(QLatin1String("[since ")) |
| && p1.endsWith(QLatin1String("]"))) { |
| p2 = p1.mid(7, p1.length() - 8); |
| p1 = getArgument(); |
| } |
| if (!priv->enumItemList.contains(p1)) |
| priv->enumItemList.append(p1); |
| |
| openedLists.top().next(); |
| append(Atom::ListTagLeft, ATOM_LIST_VALUE); |
| append(Atom::String, p1); |
| append(Atom::ListTagRight, ATOM_LIST_VALUE); |
| if (!p2.isEmpty()) { |
| append(Atom::SinceTagLeft, ATOM_LIST_VALUE); |
| append(Atom::String, p2); |
| append(Atom::SinceTagRight, ATOM_LIST_VALUE); |
| } |
| append(Atom::ListItemLeft, ATOM_LIST_VALUE); |
| |
| skipSpacesOrOneEndl(); |
| if (isBlankLine()) |
| append(Atom::Nop); |
| } else { |
| // ### unknown problems |
| } |
| break; |
| case CMD_WARNING: |
| leavePara(); |
| enterPara(); |
| append(Atom::FormattingLeft, ATOM_FORMATTING_BOLD); |
| append(Atom::String, "Warning:"); |
| append(Atom::FormattingRight, ATOM_FORMATTING_BOLD); |
| append(Atom::String, " "); |
| break; |
| case CMD_OVERLOAD: |
| priv->metacommandsUsed.insert(cmdStr); |
| p1.clear(); |
| if (!isBlankLine()) |
| p1 = getRestOfLine(); |
| if (!p1.isEmpty()) { |
| append(Atom::ParaLeft); |
| append(Atom::String, "This function overloads "); |
| append(Atom::AutoLink, p1); |
| append(Atom::String, "."); |
| append(Atom::ParaRight); |
| } else { |
| append(Atom::ParaLeft); |
| append(Atom::String, "This is an overloaded function."); |
| append(Atom::ParaRight); |
| p1 = getMetaCommandArgument(cmdStr); |
| } |
| priv->metaCommandMap[cmdStr].append(ArgLocPair(p1, location())); |
| break; |
| case NOT_A_CMD: |
| if (metaCommandSet.contains(cmdStr)) { |
| priv->metacommandsUsed.insert(cmdStr); |
| // Force a linebreak after \obsolete or \deprecated |
| // to treat potential arguments as a new text paragraph. |
| if (pos < len && |
| (cmdStr == QLatin1String("obsolete") || |
| cmdStr == QLatin1String("deprecated"))) |
| input_[pos] = '\n'; |
| QString arg = getMetaCommandArgument(cmdStr); |
| priv->metaCommandMap[cmdStr].append(ArgLocPair(arg, location())); |
| if (possibleTopics.contains(cmdStr)) { |
| if (!cmdStr.endsWith(QLatin1String("propertygroup"))) |
| priv->topics_.append(Topic(cmdStr, arg)); |
| } |
| } else if (macroHash()->contains(cmdStr)) { |
| const Macro ¯o = macroHash()->value(cmdStr); |
| int numPendingFi = 0; |
| int numFormatDefs = 0; |
| QString matchExpr; |
| for (auto it = macro.otherDefs.constBegin(); |
| it != macro.otherDefs.constEnd(); ++it) { |
| if (it.key() == "match") { |
| matchExpr = it.value(); |
| } else { |
| append(Atom::FormatIf, it.key()); |
| expandMacro(cmdStr, *it, macro.numParams); |
| ++numFormatDefs; |
| if (it == macro.otherDefs.constEnd()) { |
| append(Atom::FormatEndif); |
| } else { |
| append(Atom::FormatElse); |
| ++numPendingFi; |
| } |
| } |
| } |
| while (numPendingFi-- > 0) |
| append(Atom::FormatEndif); |
| |
| if (!macro.defaultDef.isEmpty()) { |
| if (numFormatDefs > 0) { |
| macro.defaultDefLocation.warning(tr("Macro cannot have both " |
| "format-specific and qdoc-" |
| "syntax definitions")); |
| } else { |
| QString expanded = expandMacroToString(cmdStr, macro.defaultDef, |
| macro.numParams, matchExpr); |
| input_.replace(backslashPos, endPos - backslashPos, expanded); |
| len = input_.length(); |
| pos = backslashPos; |
| } |
| } |
| } else if (isAutoLinkString(cmdStr)) { |
| appendWord(cmdStr); |
| } else { |
| if (!cmdStr.endsWith("propertygroup")) { |
| // The QML and JS property group commands are no longer required |
| // for grouping QML and JS properties. They are allowed but ignored. |
| location().warning(tr("Unknown command '\\%1'").arg(cmdStr), |
| detailsUnknownCommand(metaCommandSet, cmdStr)); |
| } |
| enterPara(); |
| append(Atom::UnknownCommand, cmdStr); |
| } |
| } |
| } // case '\\' (qdoc markup command) |
| break; |
| } |
| case '{': |
| enterPara(); |
| appendChar('{'); |
| ++braceDepth; |
| ++pos; |
| break; |
| case '}': { |
| --braceDepth; |
| ++pos; |
| |
| auto format = pendingFormats.find(braceDepth); |
| if (format == pendingFormats.end()) { |
| enterPara(); |
| appendChar('}'); |
| } else { |
| append(Atom::FormattingRight, *format); |
| if (*format == ATOM_FORMATTING_INDEX) { |
| if (indexStartedPara) |
| skipAllSpaces(); |
| } else if (*format == 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 = nullptr; |
| } |
| pendingFormats.erase(format); |
| } |
| break; |
| } |
| // Do not parse content after '//!' comments |
| case '/': { |
| if (pos + 2 < len) |
| if (input_.at(pos + 1) == '/') |
| if (input_.at(pos + 2) == '!') { |
| pos += 2; |
| getRestOfLine(); |
| break; |
| } |
| Q_FALLTHROUGH(); // fall through |
| } |
| default: { |
| bool newWord; |
| switch (priv->text.lastAtom()->type()) { |
| case Atom::ParaLeft: |
| newWord = true; |
| break; |
| default: |
| newWord = false; |
| } |
| |
| if (paraState == OutsideParagraph) { |
| if (ch.isSpace()) { |
| ++pos; |
| newWord = false; |
| } else { |
| enterPara(); |
| newWord = true; |
| } |
| } else { |
| if (ch.isSpace()) { |
| ++pos; |
| if ((ch == '\n') && (paraState == InSingleLineParagraph || isBlankLine())) { |
| leavePara(); |
| newWord = false; |
| } else { |
| appendChar(' '); |
| newWord = true; |
| } |
| } else { |
| newWord = true; |
| } |
| } |
| |
| if (newWord) { |
| int startPos = pos; |
| bool autolink = isAutoLinkString(input_, pos); |
| if (pos == startPos) { |
| if (!ch.isSpace()) { |
| appendChar(ch); |
| ++pos; |
| } |
| } else { |
| QString word = input_.mid(startPos, pos - startPos); |
| if (autolink) { |
| if (ignorewords.contains(word) || word.startsWith(QString("__"))) |
| appendWord(word); |
| else |
| append(Atom::AutoLink, word); |
| } else { |
| appendWord(word); |
| } |
| } |
| } |
| } // default: |
| } // switch (ch.unicode()) |
| } |
| 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))); |
| } |
| |
| if (currentSection > Doc::NoSection) { |
| append(Atom::SectionRight, QString::number(currentSection)); |
| currentSection = Doc::NoSection; |
| } |
| |
| if (priv->extra && priv->extra->granularity_ < priv->extra->section_) |
| priv->extra->granularity_ = priv->extra->section_; |
| priv->text.stripFirstAtom(); |
| } |
| |
| /*! |
| Returns the current location. |
| */ |
| Location &DocParser::location() |
| { |
| while (!openedInputs.isEmpty() && openedInputs.top() <= pos) { |
| cachedLoc.pop(); |
| cachedPos = openedInputs.pop(); |
| } |
| while (cachedPos < pos) |
| cachedLoc.advance(input_.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 != nullptr) { |
| 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::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()); |
| priv->constructExtra(); |
| if (keyword) { |
| append(Atom::Keyword, target); |
| priv->extra->keywords_.append(priv->text.lastAtom()); |
| } else { |
| append(Atom::Target, target); |
| priv->extra->targets_.append(priv->text.lastAtom()); |
| } |
| } |
| } |
| |
| void DocParser::include(const QString &fileName, const QString &identifier) |
| { |
| if (location().depth() > 16) |
| location().fatal(tr("Too many nested '\\%1's").arg(cmdName(CMD_INCLUDE))); |
| |
| QString userFriendlyFilePath; |
| QString filePath = Config::instance().getIncludeFilePath(fileName); |
| if (filePath.isEmpty()) { |
| location().warning(tr("Cannot find qdoc include file '%1'").arg(fileName)); |
| } else { |
| QFile inFile(filePath); |
| if (!inFile.open(QFile::ReadOnly)) { |
| location().warning(tr("Cannot open qdoc include file '%1'").arg(userFriendlyFilePath)); |
| } else { |
| location().push(userFriendlyFilePath); |
| |
| QTextStream inStream(&inFile); |
| QString includedStuff = inStream.readAll(); |
| inFile.close(); |
| |
| if (identifier.isEmpty()) { |
| input_.insert(pos, includedStuff); |
| len = input_.length(); |
| openedInputs.push(pos + includedStuff.length()); |
| } else { |
| QStringList lineBuffer = includedStuff.split(QLatin1Char('\n')); |
| int i = 0; |
| int startLine = -1; |
| while (i < lineBuffer.size()) { |
| if (lineBuffer[i].startsWith("//!")) { |
| if (lineBuffer[i].contains(identifier)) { |
| startLine = i + 1; |
| break; |
| } |
| } |
| ++i; |
| } |
| if (startLine < 0) { |
| location().warning(tr("Cannot find '%1' in '%2'") |
| .arg(identifier) |
| .arg(userFriendlyFilePath)); |
| return; |
| } |
| QString result; |
| i = startLine; |
| do { |
| if (lineBuffer[i].startsWith("//!")) { |
| if (i < lineBuffer.size()) { |
| if (lineBuffer[i].contains(identifier)) { |
| break; |
| } |
| } |
| } else |
| result += lineBuffer[i] + QLatin1Char('\n'); |
| ++i; |
| } while (i < lineBuffer.size()); |
| if (result.isEmpty()) { |
| location().warning(tr("Empty qdoc snippet '%1' in '%2'") |
| .arg(identifier) |
| .arg(userFriendlyFilePath)); |
| } else { |
| input_.insert(pos, result); |
| len = input_.length(); |
| openedInputs.push(pos + result.length()); |
| } |
| } |
| } |
| } |
| } |
| |
| void DocParser::startFormat(const QString &format, int cmd) |
| { |
| enterPara(); |
| |
| for (const auto &item : qAsConst(pendingFormats)) { |
| if (item == format) { |
| location().warning(tr("Cannot nest '\\%1' commands").arg(cmdName(cmd))); |
| return; |
| } |
| } |
| |
| 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_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; |
| } else if (outer == CMD_TOPICREF) |
| ok = (cmd == CMD_TOPICREF || cmd == CMD_MAPREF); |
| else if (outer == CMD_MAPREF) |
| ok = false; |
| } |
| |
| if (ok) { |
| openedCommands.push(cmd); |
| } else { |
| location().warning(tr("Can't use '\\%1' in '\\%2'").arg(cmdName(cmd)).arg(cmdName(outer))); |
| } |
| return ok; |
| } |
| |
| /*! |
| Returns \c true if \a word qualifies for auto-linking. |
| */ |
| inline bool DocParser::isAutoLinkString(const QString &word) |
| { |
| int start = 0; |
| return isAutoLinkString(word, start); |
| } |
| |
| bool DocParser::isAutoLinkString(const QString &word, int &curPos) |
| { |
| int len = word.size(); |
| int startPos = curPos; |
| int numUppercase = 0; |
| int numLowercase = 0; |
| int numStrangeSymbols = 0; |
| |
| while (curPos < len) { |
| unsigned char latin1Ch = word.at(curPos).toLatin1(); |
| if (islower(latin1Ch)) { |
| ++numLowercase; |
| ++curPos; |
| } else if (isupper(latin1Ch)) { |
| if (curPos > startPos) |
| ++numUppercase; |
| ++curPos; |
| } else if (isdigit(latin1Ch)) { |
| if (curPos > startPos) |
| ++curPos; |
| else |
| break; |
| } else if (latin1Ch == '_' || latin1Ch == '@') { |
| ++numStrangeSymbols; |
| ++curPos; |
| } else if ((latin1Ch == ':') && (curPos < len - 1) |
| && (word.at(curPos + 1) == QLatin1Char(':'))) { |
| ++numStrangeSymbols; |
| curPos += 2; |
| } else if (latin1Ch == '(') { |
| if (curPos > startPos) { |
| if ((curPos < len - 1) && (word.at(curPos + 1) == QLatin1Char(')'))) { |
| ++numStrangeSymbols; |
| pos += 2; |
| break; |
| } else { |
| break; |
| } |
| } else { |
| break; |
| } |
| } else { |
| break; |
| } |
| } |
| return ((numUppercase >= 1 && numLowercase >= 2) || numStrangeSymbols > 0); |
| } |
| |
| 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::Sections unit, int cmd) |
| { |
| leaveValueList(); |
| |
| if (currentSection == Doc::NoSection) { |
| currentSection = (Doc::Sections)(unit); |
| priv->constructExtra(); |
| priv->extra->section_ = currentSection; |
| } else |
| endSection(unit, cmd); |
| |
| append(Atom::SectionLeft, QString::number(unit)); |
| priv->constructExtra(); |
| priv->extra->tableOfContents_.append(priv->text.lastAtom()); |
| priv->extra->tableOfContentsLevels_.append(unit); |
| enterPara(Atom::SectionHeadingLeft, Atom::SectionHeadingRight, QString::number(unit)); |
| currentSection = unit; |
| } |
| |
| void DocParser::endSection(int, int) // (int unit, int endCmd) |
| { |
| leavePara(); |
| append(Atom::SectionRight, QString::number(currentSection)); |
| currentSection = (Doc::NoSection); |
| } |
| |
| void DocParser::parseAlso() |
| { |
| leavePara(); |
| skipSpacesOnLine(); |
| while (pos < len && input_[pos] != '\n') { |
| QString target; |
| QString str; |
| bool skipMe = false; |
| |
| if (input_[pos] == '{') { |
| target = getArgument(); |
| skipSpacesOnLine(); |
| if (pos < len && input_[pos] == '{') { |
| str = getArgument(); |
| |
| // hack for C++ to support links like \l{QString::}{count()} |
| if (target.endsWith("::")) |
| target += str; |
| } else { |
| str = target; |
| } |
| } else { |
| target = getArgument(); |
| str = cleanLink(target); |
| if (target == QLatin1String("and") || target == QLatin1String(".")) |
| skipMe = true; |
| } |
| |
| if (!skipMe) { |
| 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 && input_[pos] == ',') { |
| pos++; |
| skipSpacesOrOneEndl(); |
| } else if (pos >= len || input_[pos] != '\n') { |
| location().warning(tr("Missing comma in '\\%1'").arg(cmdName(CMD_SA))); |
| } |
| } |
| } |
| |
| void DocParser::append(Atom::AtomType type, const QString &string) |
| { |
| Atom::AtomType lastType = priv->text.lastAtom()->type(); |
| if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n"))) |
| priv->text.lastAtom()->chopString(); |
| priv->text << Atom(type, string); |
| } |
| |
| void DocParser::append(const QString &string) |
| { |
| Atom::AtomType lastType = priv->text.lastAtom()->type(); |
| if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n"))) |
| priv->text.lastAtom()->chopString(); |
| priv->text << Atom(Atom::Link, string); |
| } |
| |
| void DocParser::append(Atom::AtomType type, const QString &p1, const QString &p2) |
| { |
| Atom::AtomType lastType = priv->text.lastAtom()->type(); |
| if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n"))) |
| priv->text.lastAtom()->chopString(); |
| priv->text << Atom(type, p1, p2); |
| } |
| |
| void DocParser::append(const QString &p1, const QString &p2) |
| { |
| Atom::AtomType lastType = priv->text.lastAtom()->type(); |
| if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n"))) |
| priv->text.lastAtom()->chopString(); |
| if (p2.isEmpty()) |
| priv->text << Atom(Atom::Link, p1); |
| else |
| priv->text << LinkAtom(p1, p2); |
| } |
| |
| 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) |
| { |
| if (!isCode(lastAtom)) { |
| append(Atom::Code); |
| lastAtom = priv->text.lastAtom(); |
| } |
| lastAtom->appendString(markedCode); |
| } |
| |
| void DocParser::appendToCode(const QString &markedCode, Atom::AtomType defaultType) |
| { |
| if (!isCode(lastAtom)) { |
| append(defaultType, markedCode); |
| lastAtom = priv->text.lastAtom(); |
| } else { |
| lastAtom->appendString(markedCode); |
| } |
| } |
| |
| void DocParser::startNewPara() |
| { |
| leavePara(); |
| enterPara(); |
| } |
| |
| void DocParser::enterPara(Atom::AtomType leftType, Atom::AtomType rightType, const QString &string) |
| { |
| if (paraState == OutsideParagraph) { |
| |
| if ((priv->text.lastAtom()->type() != Atom::ListItemLeft) |
| && (priv->text.lastAtom()->type() != Atom::DivLeft)) { |
| leaveValueList(); |
| } |
| |
| append(leftType, string); |
| indexStartedPara = false; |
| pendingParaLeftType = leftType; |
| pendingParaRightType = rightType; |
| pendingParaString = string; |
| if (leftType == Atom::SectionHeadingLeft) { |
| paraState = InSingleLineParagraph; |
| } else { |
| paraState = InMultiLineParagraph; |
| } |
| skipSpacesOrOneEndl(); |
| } |
| } |
| |
| void DocParser::leavePara() |
| { |
| if (paraState != OutsideParagraph) { |
| 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(QLatin1Char(' '))) { |
| priv->text.lastAtom()->chopString(); |
| } |
| append(pendingParaRightType, pendingParaString); |
| } |
| paraState = OutsideParagraph; |
| indexStartedPara = false; |
| pendingParaRightType = Atom::Nop; |
| pendingParaString.clear(); |
| } |
| } |
| |
| 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()); |
| } |
| |
| /*! |
| Expands a macro in-place in input. |
| |
| Expects the current \e pos in the input to point to a backslash, and the macro to have a |
| default definition. Format-specific macros are currently not expanded. |
| |
| \note In addition to macros, a valid use for a backslash in an argument include |
| escaping non-alnum characters, and splitting a single argument across multiple |
| lines by escaping newlines. Escaping is also handled here. |
| |
| Returns \c true on successful macro expansion. |
| */ |
| bool DocParser::expandMacro() |
| { |
| Q_ASSERT(input_[pos].unicode() == '\\'); |
| |
| QString cmdStr; |
| int backslashPos = pos++; |
| while (pos < input_.length() && input_[pos].isLetterOrNumber()) |
| cmdStr += input_[pos++]; |
| |
| endPos = pos; |
| if (!cmdStr.isEmpty()) { |
| if (macroHash()->contains(cmdStr)) { |
| const Macro ¯o = macroHash()->value(cmdStr); |
| if (!macro.defaultDef.isEmpty()) { |
| QString expanded = expandMacroToString(cmdStr, macro.defaultDef, macro.numParams, |
| macro.otherDefs.value("match")); |
| input_.replace(backslashPos, pos - backslashPos, expanded); |
| len = input_.length(); |
| pos = backslashPos; |
| return true; |
| } else { |
| location().warning(tr("Macro '%1' does not have a default definition").arg(cmdStr)); |
| } |
| } else { |
| location().warning(tr("Unknown macro '%1'").arg(cmdStr)); |
| pos = ++backslashPos; |
| } |
| } else if (input_[pos].isSpace()) { |
| skipAllSpaces(); |
| } else if (input_[pos].unicode() == '\\') { |
| // allow escaping a backslash |
| input_.remove(pos--, 1); |
| --len; |
| } |
| return false; |
| } |
| |
| 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(); |
| } else { |
| location().warning(tr("Macro '\\%1' invoked with too few" |
| " arguments (expected %2, got %3)") |
| .arg(name) |
| .arg(numParams) |
| .arg(i)); |
| numParams = i; |
| break; |
| } |
| } |
| |
| int j = 0; |
| while (j < def.size()) { |
| int paramNo; |
| if (((paramNo = def[j].unicode()) >= 1) && (paramNo <= numParams)) { |
| if (!rawString.isEmpty()) { |
| append(Atom::RawString, rawString); |
| rawString.clear(); |
| } |
| append(Atom::String, args[paramNo - 1]); |
| j += 1; |
| } else { |
| rawString += def[j++]; |
| } |
| } |
| if (!rawString.isEmpty()) |
| append(Atom::RawString, rawString); |
| } |
| } |
| |
| QString DocParser::expandMacroToString(const QString &name, const QString &def, int numParams, |
| const QString &matchExpr) |
| { |
| QString rawString; |
| |
| if (numParams == 0) { |
| rawString = def; |
| } else { |
| QStringList args; |
| 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)); |
| numParams = i; |
| break; |
| } |
| } |
| |
| int j = 0; |
| while (j < def.size()) { |
| int paramNo; |
| if (((paramNo = def[j].unicode()) >= 1) && (paramNo <= numParams)) { |
| rawString += args[paramNo - 1]; |
| j += 1; |
| } else { |
| rawString += def[j++]; |
| } |
| } |
| } |
| if (matchExpr.isEmpty()) |
| return rawString; |
| |
| QString result; |
| QRegExp re(matchExpr); |
| int capStart = (re.captureCount() > 0) ? 1 : 0; |
| int i = 0; |
| while ((i = re.indexIn(rawString, i)) != -1) { |
| for (int c = capStart; c <= re.captureCount(); ++c) |
| result += re.cap(c); |
| i += re.matchedLength(); |
| } |
| |
| return result; |
| } |
| |
| Doc::Sections DocParser::getSectioningUnit() |
| { |
| QString name = getOptionalArgument(); |
| |
| 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::NoSection; |
| } else { |
| location().warning(tr("Invalid section '%1'").arg(name)); |
| return Doc::NoSection; |
| } |
| } |
| |
| /*! |
| Gets an argument that is enclosed in braces and returns it |
| without the enclosing braces. On entry, the current character |
| is the left brace. On exit, the current character is the one |
| that comes after the right brace. |
| |
| If \a verbatim is true, extra whitespace is retained in the |
| returned string. Otherwise, extra whitespace is removed. |
| */ |
| QString DocParser::getBracedArgument(bool verbatim) |
| { |
| QString arg; |
| int delimDepth = 0; |
| if (pos < input_.length() && input_[pos] == '{') { |
| ++pos; |
| while (pos < input_.length() && delimDepth >= 0) { |
| switch (input_[pos].unicode()) { |
| case '{': |
| ++delimDepth; |
| arg += QLatin1Char('{'); |
| ++pos; |
| break; |
| case '}': |
| --delimDepth; |
| if (delimDepth >= 0) |
| arg += QLatin1Char('}'); |
| ++pos; |
| break; |
| case '\\': |
| if (verbatim || !expandMacro()) |
| arg += input_[pos++]; |
| break; |
| default: |
| if (input_[pos].isSpace() && !verbatim) |
| arg += QChar(' '); |
| else |
| arg += input_[pos]; |
| ++pos; |
| } |
| } |
| if (delimDepth > 0) |
| location().warning(tr("Missing '}'")); |
| } |
| endPos = pos; |
| return arg; |
| } |
| |
| /*! |
| 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. |
| */ |
| QString DocParser::getArgument(bool verbatim) |
| { |
| skipSpacesOrOneEndl(); |
| |
| int delimDepth = 0; |
| int startPos = pos; |
| QString arg = getBracedArgument(verbatim); |
| if (arg.isEmpty()) { |
| while ((pos < input_.length()) |
| && ((delimDepth > 0) || ((delimDepth == 0) && !input_[pos].isSpace()))) { |
| switch (input_[pos].unicode()) { |
| case '(': |
| case '[': |
| case '{': |
| ++delimDepth; |
| arg += input_[pos]; |
| ++pos; |
| break; |
| case ')': |
| case ']': |
| case '}': |
| --delimDepth; |
| if (pos == startPos || delimDepth >= 0) { |
| arg += input_[pos]; |
| ++pos; |
| } |
| break; |
| case '\\': |
| if (verbatim || !expandMacro()) |
| arg += input_[pos++]; |
| break; |
| default: |
| arg += input_[pos]; |
| ++pos; |
| } |
| } |
| endPos = pos; |
| if ((arg.length() > 1) && (QString(".,:;!?").indexOf(input_[pos - 1]) != -1) |
| && !arg.endsWith("...")) { |
| arg.truncate(arg.length() - 1); |
| --pos; |
| } |
| if (arg.length() > 2 && input_.mid(pos - 2, 2) == "'s") { |
| arg.truncate(arg.length() - 2); |
| pos -= 2; |
| } |
| } |
| return arg.simplified(); |
| } |
| |
| /*! |
| Gets an argument that is enclosed in brackets and returns it |
| without the enclosing brackets. On entry, the current character |
| is the left bracket. On exit, the current character is the one |
| that comes after the right bracket. |
| */ |
| QString DocParser::getBracketedArgument() |
| { |
| QString arg; |
| int delimDepth = 0; |
| skipSpacesOrOneEndl(); |
| if (pos < input_.length() && input_[pos] == '[') { |
| ++pos; |
| while (pos < input_.length() && delimDepth >= 0) { |
| switch (input_[pos].unicode()) { |
| case '[': |
| ++delimDepth; |
| arg += QLatin1Char('['); |
| ++pos; |
| break; |
| case ']': |
| --delimDepth; |
| if (delimDepth >= 0) |
| arg += QLatin1Char(']'); |
| ++pos; |
| break; |
| case '\\': |
| arg += input_[pos]; |
| ++pos; |
| break; |
| default: |
| arg += input_[pos]; |
| ++pos; |
| } |
| } |
| if (delimDepth > 0) |
| location().warning(tr("Missing ']'")); |
| } |
| return arg; |
| } |
| |
| QString DocParser::getOptionalArgument() |
| { |
| skipSpacesOrOneEndl(); |
| if (pos + 1 < input_.length() && input_[pos] == '\\' && input_[pos + 1].isLetterOrNumber()) { |
| return QString(); |
| } else { |
| return getArgument(); |
| } |
| } |
| |
| QString DocParser::getRestOfLine() |
| { |
| QString t; |
| |
| skipSpacesOnLine(); |
| |
| bool trailingSlash = false; |
| |
| do { |
| int begin = pos; |
| |
| while (pos < input_.size() && input_[pos] != '\n') { |
| if (input_[pos] == '\\' && !trailingSlash) { |
| trailingSlash = true; |
| ++pos; |
| while ((pos < input_.size()) && input_[pos].isSpace() && (input_[pos] != '\n')) |
| ++pos; |
| } else { |
| trailingSlash = false; |
| ++pos; |
| } |
| } |
| |
| if (!t.isEmpty()) |
| t += QLatin1Char(' '); |
| t += input_.mid(begin, pos - begin).simplified(); |
| |
| if (trailingSlash) { |
| t.chop(1); |
| t = t.simplified(); |
| } |
| if (pos < input_.size()) |
| ++pos; |
| } while (pos < input_.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 < input_.size() && (input_[pos] != '\n' || parenDepth > 0)) { |
| if (input_.at(pos) == '(') |
| ++parenDepth; |
| else if (input_.at(pos) == ')') |
| --parenDepth; |
| else if (input_.at(pos) == '\\' && expandMacro()) |
| continue; |
| ++pos; |
| } |
| if (pos == input_.size() && parenDepth > 0) { |
| pos = begin; |
| location().warning(tr("Unbalanced parentheses in '%1'").arg(cmdStr)); |
| } |
| |
| QString t = input_.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(input_, pos); |
| |
| if (end == -1) { |
| location().warning(tr("Missing '\\%1'").arg(cmdName(endCmd))); |
| pos = input_.length(); |
| } else { |
| t = input_.mid(pos, end - pos); |
| pos = end + rx.matchedLength(); |
| } |
| return t; |
| } |
| |
| QString DocParser::getCode(int cmd, CodeMarker *marker, const QString &argStr) |
| { |
| QString code = untabifyEtc(getUntilEnd(cmd)); |
| |
| if (!argStr.isEmpty()) { |
| QStringList args = argStr.split(" ", Qt::SkipEmptyParts); |
| int paramNo, j = 0; |
| while (j < code.size()) { |
| if (code[j] == '\\' && j < code.size() - 1 && (paramNo = code[j + 1].digitValue()) >= 1 |
| && paramNo <= args.size()) { |
| QString p = args[paramNo - 1]; |
| code.replace(j, 2, p); |
| j += qMin(1, p.size()); |
| } else { |
| ++j; |
| } |
| } |
| } |
| |
| int indent = indentLevel(code); |
| code = unindent(indent, code); |
| if (marker == nullptr) |
| marker = CodeMarker::markerForCode(code); |
| return marker->markedUpCode(code, nullptr, location()); |
| } |
| |
| /*! |
| Was used only for generating doxygen output. |
| */ |
| QString DocParser::getUnmarkedCode(int cmd) |
| { |
| QString code = getUntilEnd(cmd); |
| return code; |
| } |
| |
| bool DocParser::isBlankLine() |
| { |
| int i = pos; |
| |
| while (i < len && input_[i].isSpace()) { |
| if (input_[i] == '\n') |
| return true; |
| ++i; |
| } |
| return false; |
| } |
| |
| bool DocParser::isLeftBraceAhead() |
| { |
| int numEndl = 0; |
| int i = pos; |
| |
| while (i < len && input_[i].isSpace() && numEndl < 2) { |
| // ### bug with '\\' |
| if (input_[i] == '\n') |
| numEndl++; |
| ++i; |
| } |
| return numEndl < 2 && i < len && input_[i] == '{'; |
| } |
| |
| bool DocParser::isLeftBracketAhead() |
| { |
| int numEndl = 0; |
| int i = pos; |
| |
| while (i < len && input_[i].isSpace() && numEndl < 2) { |
| // ### bug with '\\' |
| if (input_[i] == '\n') |
| numEndl++; |
| ++i; |
| } |
| return numEndl < 2 && i < len && input_[i] == '['; |
| } |
| |
| /*! |
| Skips to the next non-space character or EOL. |
| */ |
| void DocParser::skipSpacesOnLine() |
| { |
| while ((pos < input_.length()) && input_[pos].isSpace() && (input_[pos].unicode() != '\n')) |
| ++pos; |
| } |
| |
| /*! |
| Skips spaces and on EOL. |
| */ |
| void DocParser::skipSpacesOrOneEndl() |
| { |
| int firstEndl = -1; |
| while (pos < input_.length() && input_[pos].isSpace()) { |
| QChar ch = input_[pos]; |
| if (ch == '\n') { |
| if (firstEndl == -1) { |
| firstEndl = pos; |
| } else { |
| pos = firstEndl; |
| break; |
| } |
| } |
| ++pos; |
| } |
| } |
| |
| void DocParser::skipAllSpaces() |
| { |
| while (pos < len && input_[pos].isSpace()) |
| ++pos; |
| } |
| |
| void DocParser::skipToNextPreprocessorCommand() |
| { |
| QRegExp rx("\\\\(?:" + cmdName(CMD_IF) + QLatin1Char('|') + cmdName(CMD_ELSE) + QLatin1Char('|') |
| + cmdName(CMD_ENDIF) + ")\\b"); |
| int end = rx.indexIn(input_, pos + 1); // ### + 1 necessary? |
| |
| if (end == -1) |
| pos = input_.length(); |
| else |
| pos = end; |
| } |
| |
| int DocParser::endCmdFor(int cmd) |
| { |
| switch (cmd) { |
| case CMD_BADCODE: |
| return CMD_ENDCODE; |
| case CMD_CODE: |
| return CMD_ENDCODE; |
| case CMD_DIV: |
| return CMD_ENDDIV; |
| case CMD_QML: |
| return CMD_ENDQML; |
| case CMD_QMLTEXT: |
| return CMD_ENDQMLTEXT; |
| case CMD_JS: |
| return CMD_ENDJS; |
| 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_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; |
| case CMD_TOPICREF: |
| return CMD_ENDTOPICREF; |
| case CMD_MAPREF: |
| return CMD_ENDMAPREF; |
| 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(QLatin1Char('\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 < 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 < 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(QLatin1Char('/'), "\\/"); |
| return QLatin1Char('/') + result + QLatin1Char('/'); |
| } |
| |
| /*! |
| Returns \c true if \a atom represents a code snippet. |
| */ |
| bool DocParser::isCode(const Atom *atom) |
| { |
| Atom::AtomType type = atom->type(); |
| return (type == Atom::Code || type == Atom::Qml || type == Atom::JavaScript); |
| } |
| |
| /*! |
| Returns \c true if \a atom represents quoting information. |
| */ |
| bool DocParser::isQuote(const Atom *atom) |
| { |
| Atom::AtomType type = atom->type(); |
| return (type == Atom::CodeQuoteArgument || type == Atom::CodeQuoteCommand |
| || type == Atom::SnippetCommand || type == Atom::SnippetIdentifier |
| || type == Atom::SnippetLocation); |
| } |
| |
| /*! |
| Parse the qdoc comment \a source. Build up a list of all the topic |
| commands found including their arguments. This constructor is used |
| when there can be more than one topic command in theqdoc comment. |
| Normally, there is only one topic command in a qdoc comment, but in |
| QML documentation, there is the case where the qdoc \e{qmlproperty} |
| command can appear multiple times in a qdoc comment. |
| */ |
| Doc::Doc(const Location &start_loc, const Location &end_loc, const QString &source, |
| const QSet<QString> &metaCommandSet, const QSet<QString> &topics) |
| { |
| priv = new DocPrivate(start_loc, end_loc, source); |
| DocParser parser; |
| parser.parse(source, priv, metaCommandSet, topics); |
| } |
| |
| Doc::Doc(const Doc &doc) : priv(nullptr) |
| { |
| 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::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 == nullptr ? dummy : priv->start_loc; |
| } |
| |
| /*! |
| Returns the starting location of a qdoc comment. |
| */ |
| const Location &Doc::startLocation() const |
| { |
| return location(); |
| } |
| |
| /*! |
| Returns the ending location of a qdoc comment. |
| */ |
| const Location &Doc::endLocation() const |
| { |
| static const Location dummy; |
| return priv == nullptr ? dummy : priv->end_loc; |
| } |
| |
| const QString &Doc::source() const |
| { |
| static QString null; |
| return priv == nullptr ? null : priv->src; |
| } |
| |
| bool Doc::isEmpty() const |
| { |
| return priv == nullptr || priv->src.isEmpty(); |
| } |
| |
| const Text &Doc::body() const |
| { |
| static const Text dummy; |
| return priv == nullptr ? dummy : priv->text; |
| } |
| |
| Text Doc::briefText(bool inclusive) const |
| { |
| return body().subText(Atom::BriefLeft, Atom::BriefRight, nullptr, inclusive); |
| } |
| |
| 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; |
| /* |
| This code is really ugly. The entire \brief business |
| should be rethought. |
| */ |
| while (atom) { |
| if (atom->type() == Atom::AutoLink || atom->type() == Atom::String) { |
| briefStr += atom->string(); |
| } else if (atom->type() == Atom::C) { |
| briefStr += Generator::plainCode(atom->string()); |
| } |
| atom = atom->next(); |
| } |
| |
| QStringList w = briefStr.split(QLatin1Char(' ')); |
| if (!w.isEmpty() && w.first() == "Returns") { |
| } else { |
| if (!w.isEmpty() && w.first() == "The") |
| w.removeFirst(); |
| |
| if (!w.isEmpty() && (w.first() == className || w.first() == classNameOnly)) |
| w.removeFirst(); |
| |
| if (!w.isEmpty() |
| && ((w.first() == "class") || (w.first() == "function") || (w.first() == "macro") |
| || (w.first() == "widget") || (w.first() == "namespace") |
| || (w.first() == "header"))) |
| w.removeFirst(); |
| |
| 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(QLatin1Char('.'))) |
| whats.truncate(whats.length() - 1); |
| |
| if (!whats.isEmpty()) |
| whats[0] = whats[0].toUpper(); |
| |
| // ### move this once \brief is abolished for properties |
| resultText << whats; |
| } |
| return resultText; |
| } |
| |
| Text Doc::legaleseText() const |
| { |
| if (priv == nullptr || !priv->hasLegalese) |
| return Text(); |
| else |
| return body().subText(Atom::LegaleseLeft, Atom::LegaleseRight); |
| } |
| |
| Doc::Sections Doc::granularity() const |
| { |
| if (priv == nullptr || priv->extra == nullptr) { |
| return DocPrivateExtra().granularity_; |
| } else { |
| return priv->extra->granularity_; |
| } |
| } |
| |
| const QSet<QString> &Doc::parameterNames() const |
| { |
| return priv == nullptr ? *null_Set_QString() : priv->params; |
| } |
| |
| const QStringList &Doc::enumItemNames() const |
| { |
| return priv == nullptr ? *null_QStringList() : priv->enumItemList; |
| } |
| |
| const QStringList &Doc::omitEnumItemNames() const |
| { |
| return priv == nullptr ? *null_QStringList() : priv->omitEnumItemList; |
| } |
| |
| const QSet<QString> &Doc::metaCommandsUsed() const |
| { |
| return priv == nullptr ? *null_Set_QString() : priv->metacommandsUsed; |
| } |
| |
| /*! |
| Returns true if the set of metacommands used in the doc |
| comment contains \e {internal}. |
| */ |
| bool Doc::isInternal() const |
| { |
| return metaCommandsUsed().contains(QLatin1String("internal")); |
| } |
| |
| /*! |
| Returns true if the set of metacommands used in the doc |
| comment contains \e {reimp}. |
| */ |
| bool Doc::isMarkedReimp() const |
| { |
| return metaCommandsUsed().contains(QLatin1String("reimp")); |
| } |
| |
| /*! |
| Returns a reference to the list of topic commands used in the |
| current qdoc comment. Normally there is only one, but there |
| can be multiple \e{qmlproperty} commands, for example. |
| */ |
| const TopicList &Doc::topicsUsed() const |
| { |
| return priv == nullptr ? *nullTopicList() : priv->topics_; |
| } |
| |
| ArgList Doc::metaCommandArgs(const QString &metacommand) const |
| { |
| return priv == nullptr ? ArgList() : priv->metaCommandMap.value(metacommand); |
| } |
| |
| const QVector<Text> &Doc::alsoList() const |
| { |
| return priv == nullptr ? *null_QVector_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 QVector<Atom *> &Doc::tableOfContents() const |
| { |
| priv->constructExtra(); |
| return priv->extra->tableOfContents_; |
| } |
| |
| const QVector<int> &Doc::tableOfContentsLevels() const |
| { |
| priv->constructExtra(); |
| return priv->extra->tableOfContentsLevels_; |
| } |
| |
| const QVector<Atom *> &Doc::keywords() const |
| { |
| priv->constructExtra(); |
| return priv->extra->keywords_; |
| } |
| |
| const QVector<Atom *> &Doc::targets() const |
| { |
| priv->constructExtra(); |
| return priv->extra->targets_; |
| } |
| |
| const QStringMultiMap &Doc::metaTagMap() const |
| { |
| return priv && priv->extra ? priv->extra->metaMap_ : *null_QStringMultiMap(); |
| } |
| |
| void Doc::initialize() |
| { |
| Config &config = Config::instance(); |
| DocParser::tabSize = config.getInt(CONFIG_TABSIZE); |
| DocParser::exampleFiles = config.getCanonicalPathList(CONFIG_EXAMPLES); |
| DocParser::exampleDirs = config.getCanonicalPathList(CONFIG_EXAMPLEDIRS); |
| DocParser::sourceFiles = config.getCanonicalPathList(CONFIG_SOURCES); |
| DocParser::sourceDirs = config.getCanonicalPathList(CONFIG_SOURCEDIRS); |
| DocParser::ignorewords = config.getStringList(CONFIG_IGNOREWORDS); |
| |
| QmlTypeNode::qmlOnly = config.getBool(CONFIG_QMLONLY); |
| QStringMap reverseAliasMap; |
| |
| for (const auto &a : config.subVars(CONFIG_ALIAS)) { |
| QString alias = config.getString(CONFIG_ALIAS + Config::dot + a); |
| if (reverseAliasMap.contains(alias)) { |
| config.lastLocation().warning(tr("Command name '\\%1' cannot stand" |
| " for both '\\%2' and '\\%3'") |
| .arg(alias) |
| .arg(reverseAliasMap[alias]) |
| .arg(a)); |
| } else { |
| reverseAliasMap.insert(alias, a); |
| } |
| aliasMap()->insert(a, alias); |
| } |
| |
| 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; |
| } |
| |
| for (const auto ¯oName : config.subVars(CONFIG_MACRO)) { |
| QString macroDotName = CONFIG_MACRO + Config::dot + macroName; |
| 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; |
| |
| for (const auto &f : config.subVars(macroDotName)) { |
| QString def = config.getString(macroDotName + Config::dot + f); |
| if (!def.isEmpty()) { |
| macro.otherDefs.insert(f, def); |
| int m = Config::numParams(def); |
| 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.constBegin().key(); |
| config.lastLocation().warning(tr("Macro '\\%1' takes" |
| " inconsistent number" |
| " of arguments (%2" |
| " %3, %4 %5)") |
| .arg(macroName) |
| .arg(f) |
| .arg(m) |
| .arg(other) |
| .arg(macro.numParams)); |
| silent = true; |
| } |
| if (macro.numParams < m) |
| macro.numParams = m; |
| } |
| } |
| } |
| if (macro.numParams != -1) |
| macroHash()->insert(macroName, macro); |
| } |
| // If any of the formats define quotinginformation, activate quoting |
| DocParser::quoting = config.getBool(CONFIG_QUOTINGINFORMATION); |
| for (const auto &format : config.getOutputFormats()) |
| DocParser::quoting = DocParser::quoting |
| || config.getBool(format + Config::dot + CONFIG_QUOTINGINFORMATION); |
| } |
| |
| /*! |
| All the heap allocated variables are deleted. |
| */ |
| 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 = nullptr; |
| ++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 < 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); |
| } |
| |
| QString Doc::resolveFile(const Location &location, const QString &fileName, |
| QString *userFriendlyFilePath) |
| { |
| const QString result = Config::findFile(location, DocParser::exampleFiles, |
| DocParser::exampleDirs, fileName, userFriendlyFilePath); |
| qCDebug(lcQdoc).noquote().nospace() |
| << __FUNCTION__ << "(location=" << location.fileName() << ':' << location.lineNo() |
| << ", fileName=\"" << fileName << "\"), resolved to \"" << result; |
| return result; |
| } |
| |
| CodeMarker *Doc::quoteFromFile(const Location &location, Quoter "er, const QString &fileName) |
| { |
| quoter.reset(); |
| |
| QString code; |
| |
| QString userFriendlyFilePath; |
| const QString filePath = resolveFile(location, fileName, &userFriendlyFilePath); |
| if (filePath.isEmpty()) { |
| QString details = QLatin1String("Example directories: ") |
| + DocParser::exampleDirs.join(QLatin1Char(' ')); |
| if (!DocParser::exampleFiles.isEmpty()) |
| details += QLatin1String(", example files: ") |
| + DocParser::exampleFiles.join(QLatin1Char(' ')); |
| location.warning(tr("Cannot find file to quote from: '%1'").arg(fileName), details); |
| } else { |
| QFile inFile(filePath); |
| if (!inFile.open(QFile::ReadOnly)) { |
| location.warning(tr("Cannot open file to quote from: '%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, nullptr, location)); |
| 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 dashAppended = 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; |
| dashAppended = false; |
| lastAlnum = result.size(); |
| } else if (!dashAppended) { |
| if (begun) |
| result += QLatin1Char('-'); |
| dashAppended = true; |
| } |
| } |
| result.truncate(lastAlnum); |
| return result; |
| } |
| |
| void Doc::detach() |
| { |
| if (priv == nullptr) { |
| 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; |
| } |
| |
| /*! |
| The destructor deletes all the sub-TopicRefs. |
| */ |
| TopicRef::~TopicRef() |
| { |
| qDeleteAll(subrefs_); |
| } |
| |
| /*! |
| Returns a reference to the structure that will be used |
| for generating a DITA mao. |
| */ |
| const DitaRefList &Doc::ditamap() const |
| { |
| return priv->ditamap_; |
| } |
| |
| QT_END_NAMESPACE |