/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the tools applications of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** 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.1, 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. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $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 <qfile.h> | |
#include <qfileinfo.h> | |
#include <qhash.h> | |
#include <qtextstream.h> | |
#include <qregexp.h> | |
#include <ctype.h> | |
#include <limits.h> | |
#include <qdebug.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) | |
Q_GLOBAL_STATIC(QStringMultiMap, null_QStringMultiMap) | |
struct Macro | |
{ | |
QString defaultDef; | |
Location defaultDefLocation; | |
QStringMap otherDefs; | |
int numParams; | |
}; | |
enum { | |
CMD_A, | |
CMD_ABSTRACT, | |
CMD_ANNOTATEDLIST, | |
CMD_BADCODE, | |
CMD_BASENAME, | |
CMD_BOLD, | |
CMD_BRIEF, | |
CMD_C, | |
CMD_CAPTION, | |
CMD_CHAPTER, // 9 | |
CMD_CODE, | |
CMD_CODELINE, | |
CMD_DIV, | |
CMD_DOTS, | |
CMD_ELSE, | |
CMD_ENDABSTRACT, | |
CMD_ENDCHAPTER, | |
CMD_ENDCODE, | |
CMD_ENDDIV, | |
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, // 68 | |
CMD_SECTION2, // 69 | |
CMD_SECTION3, // 70 | |
CMD_SECTION4, // 71 | |
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_TT, | |
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, 0 }, | |
{ "abstract", CMD_ABSTRACT, 0 }, | |
{ "annotatedlist", CMD_ANNOTATEDLIST, 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}, | |
{ "div", CMD_DIV, 0 }, | |
{ "dots", CMD_DOTS, 0 }, | |
{ "else", CMD_ELSE, 0 }, | |
{ "endabstract", CMD_ENDABSTRACT, 0 }, | |
{ "endchapter", CMD_ENDCHAPTER, 0 }, | |
{ "endcode", CMD_ENDCODE, 0 }, | |
{ "enddiv", CMD_ENDDIV, 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 | |
{ "sincelist", CMD_SINCELIST, 0 }, | |
{ "skipline", CMD_SKIPLINE, 0 }, | |
{ "skipto", CMD_SKIPTO, 0 }, | |
{ "skipuntil", CMD_SKIPUNTIL, 0 }, | |
{ "snippet", CMD_SNIPPET, 0 }, | |
{ "span", CMD_SPAN, 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 }, | |
{ "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 }, | |
{ "js", CMD_JS, 0 }, | |
{ "endjs", CMD_ENDJS, 0 }, | |
{ 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::Sections granularity; | |
Doc::Sections section; // ### | |
QList<Atom*> tableOfContents; | |
QList<int> tableOfContentsLevels; | |
QList<Atom*> keywords; | |
QList<Atom*> targets; | |
QStringMultiMap 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, 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(Atom::Type type, const QString& string = ""); | |
void append(Atom::Type type, 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::Type defaultType); | |
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); | |
QString expandMacroToString(const QString &name, const QString &def, int numParams); | |
Doc::Sections 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 ParagraphState { | |
OutsideParagraph, | |
InSingleLineParagraph, | |
InMultiLineParagraph | |
}; | |
ParagraphState paraState; | |
bool inTableHeader; | |
bool inTableRow; | |
bool inTableItem; | |
bool indexStartedPara; // ### rename | |
Atom::Type pendingParaLeftType; | |
Atom::Type pendingParaRightType; | |
QString pendingParaString; | |
int braceDepth; | |
int minIndent; | |
Doc::Sections currentSection; | |
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 = OutsideParagraph; | |
inTableHeader = false; | |
inTableRow = false; | |
inTableItem = false; | |
indexStartedPara = false; | |
pendingParaLeftType = Atom::Nop; | |
pendingParaRightType = Atom::Nop; | |
braceDepth = 0; | |
minIndent = INT_MAX; | |
currentSection = Doc::NoSection; | |
openedCommands.push(CMD_OMIT); | |
quoter.reset(); | |
CodeMarker *marker = 0; | |
Atom *currentLinkAtom = 0; | |
QString p1, p2; | |
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(); | |
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_ABSTRACT: | |
if (openCommand(cmd)) { | |
leavePara(); | |
append(Atom::AbstractLeft); | |
} | |
break; | |
case CMD_BADCODE: | |
leavePara(); | |
append(Atom::CodeBad,getCode(CMD_BADCODE, marker)); | |
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(); | |
p1 = untabifyEtc(getArgument(true)); | |
marker = CodeMarker::markerForCode(p1); | |
append(Atom::C, marker->markedUpCode(p1, 0, location())); | |
break; | |
case CMD_CAPTION: | |
leavePara(); | |
enterPara(Atom::CaptionLeft, Atom::CaptionRight); | |
break; | |
case CMD_CHAPTER: | |
startSection(Doc::Chapter, cmd); | |
break; | |
case CMD_CODE: | |
leavePara(); | |
append(Atom::Code, getCode(CMD_CODE, 0)); | |
break; | |
case CMD_QML: | |
leavePara(); | |
append(Atom::Qml, getCode(CMD_QML, CodeMarker::markerForLanguage(QLatin1String("QML")))); | |
break; | |
case CMD_QMLTEXT: | |
append(Atom::QmlText); | |
break; | |
case CMD_JS: | |
leavePara(); | |
append(Atom::JavaScript, getCode(CMD_JS, CodeMarker::markerForLanguage(QLatin1String("JavaScript")))); | |
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) { | |
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, " "); | |
} | |
} | |
break; | |
case CMD_DOTS: | |
{ | |
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); | |
} | |
} | |
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(Doc::Chapter, cmd); | |
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(" ")) | |
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(Doc::Part, 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_EXPIRE: | |
checkExpiry(getArgument()); | |
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: | |
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: | |
{ | |
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 (isLeftBraceAhead()) { | |
p1 = getArgument(); | |
append(Atom::Link, p1); | |
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(Atom::Link, p1); | |
append(Atom::FormattingLeft, ATOM_FORMATTING_LINK); | |
append(Atom::String, cleanLink(p1)); | |
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(); | |
p1 = getArgument(); | |
append(Atom::Link, p1); | |
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(); | |
p1 = getArgument(); | |
priv->extra->metaMap.insert(p1, getArgument()); | |
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) { | |
p1 = "1,1"; | |
if (isLeftBraceAhead()) { | |
p1 = getArgument(); | |
if (isLeftBraceAhead()) { | |
p2 = 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, 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_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(); | |
p1 = getRestOfLine(); | |
if (!quoting) { | |
quoter.quoteTo(location(), cmdStr, | |
slashed(marker->functionBeginRegExp(p1))); | |
append(Atom::Code, | |
quoter.quoteUntil(location(), cmdStr, | |
slashed(marker->functionEndRegExp(p1)))); | |
quoter.reset(); | |
} | |
else { | |
append(Atom::CodeQuoteCommand, cmdStr); | |
append(Atom::CodeQuoteArgument, slashed(marker->functionEndRegExp(p1))); | |
} | |
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(); | |
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_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); | |
} | |
else { | |
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 = getRestOfLine(); | |
if (openCommand(cmd)) { | |
leavePara(); | |
append(Atom::TableLeft, p1); | |
inTableHeader = false; | |
inTableRow = false; | |
inTableItem = false; | |
} | |
break; | |
case CMD_TABLEOFCONTENTS: | |
p1 = "1"; | |
if (isLeftBraceAhead()) | |
p1 = getArgument(); | |
p1 += ","; | |
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_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) { | |
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); | |
append(Atom::ListItemLeft, ATOM_LIST_VALUE); | |
skipSpacesOrOneEndl(); | |
if (isBlankLine()) | |
append(Atom::Nop); | |
} | |
else { | |
// ### 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(p1); | |
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, expandMacroToString(cmdStr, macro.defaultDef, macro.numParams)); | |
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 == 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; | |
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))); | |
} | |
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(); | |
} | |
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 (currentSection == priv->extra->section) { | |
priv->extra->baseName = baseName; | |
} | |
else { | |
Atom *atom = priv->text.firstAtom(); | |
Atom *sectionLeft = 0; | |
int delta = currentSection - priv->extra->section; | |
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, const QString& identifier) | |
{ | |
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 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()) { | |
in.insert(pos, includedStuff); | |
len = in.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] + "\n"; | |
++i; | |
} while (i < lineBuffer.size()); | |
if (result.isEmpty()) { | |
location().warning(tr("Empty qdoc snippet '%1' in '%2'") | |
.arg(identifier) | |
.arg(userFriendlyFilePath)); | |
} | |
else { | |
in.insert(pos, result); | |
len = in.length(); | |
openedInputs.push(pos + result.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::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 unit, int endCmd) | |
{ | |
leavePara(); | |
append(Atom::SectionRight, QString::number(currentSection)); | |
currentSection = (Doc::NoSection); | |
} | |
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))); | |
} | |
} | |
} | |
//static bool debug = false; | |
#if 0 | |
if (type == Atom::DivLeft) | |
debug = true; | |
if (debug) | |
qDebug() << type << string; | |
if (type == Atom::DivRight) | |
debug = false; | |
#endif | |
void DocParser::append(Atom::Type type, const QString &string) | |
{ | |
Atom::Type 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(Atom::Type type, const QString& p1, const QString& p2) | |
{ | |
Atom::Type 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::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 && lastType != Atom::Code && lastType != Atom::JavaScript) | |
append(Atom::Qml); | |
#else | |
if (lastType != Atom::Code) | |
append(Atom::Code); | |
#endif | |
priv->text.lastAtom()->appendString(markedCode); | |
} | |
void DocParser::appendToCode(const QString &markedCode, Atom::Type defaultType) | |
{ | |
Atom::Type lastType = priv->text.lastAtom()->type(); | |
if (lastType != Atom::Qml && lastType != Atom::Code && lastType != Atom::JavaScript) | |
append(defaultType, markedCode); | |
else | |
priv->text.lastAtom()->appendString(markedCode); | |
} | |
void DocParser::startNewPara() | |
{ | |
leavePara(); | |
enterPara(); | |
} | |
void DocParser::enterPara(Atom::Type leftType, | |
Atom::Type 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(" ")) { | |
priv->text.lastAtom()->chopString(); | |
} | |
append(pendingParaRightType, pendingParaString); | |
} | |
paraState = OutsideParagraph; | |
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 (((paramNo = def[j].unicode()) >= 1) && | |
(paramNo <= numParams)) { | |
if (!rawString.isEmpty()) { | |
append(Atom::RawString, rawString); | |
rawString = ""; | |
} | |
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) | |
{ | |
if (numParams == 0) { | |
return 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 (((paramNo = def[j].unicode()) >= 1) && | |
(paramNo <= numParams)) { | |
rawString += args[paramNo - 1]; | |
j += 1; | |
} | |
else { | |
rawString += def[j++]; | |
} | |
} | |
return rawString; | |
} | |
} | |
Doc::Sections 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::NoSection; | |
} | |
else { | |
location().warning(tr("Invalid section '%1'").arg(name)); | |
return Doc::NoSection; | |
} | |
} | |
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); | |
if (!marker) | |
marker = CodeMarker::markerForCode(code); | |
return marker->markedUpCode(code, 0, 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 && 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] == '{'; | |
} | |
/*! | |
Skips to the next non-space character or EOL. | |
*/ | |
void DocParser::skipSpacesOnLine() | |
{ | |
while ((pos < in.length()) && | |
in[pos].isSpace() && | |
(in[pos].unicode() != '\n')) | |
++pos; | |
} | |
/*! | |
Skips spaces and on EOL. | |
*/ | |
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; | |
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_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") | |
#define COMMAND_QMLBRIEF Doc::alias("qmlbrief") | |
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); | |
} | |
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(bool inclusive) const | |
{ | |
return body().subText(Atom::BriefLeft, Atom::BriefRight, 0, 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; | |
bool standardWording = true; | |
/* | |
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(); | |
} | |
atom = atom->next(); | |
} | |
QStringList w = briefStr.split(" "); | |
if (!w.isEmpty() && w.first() == "Returns") { | |
} | |
else { | |
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() == "function") || | |
(w.first() == "macro") || | |
(w.first() == "widget") || | |
(w.first() == "namespace") || | |
(w.first() == "header"))) | |
w.removeFirst(); | |
else { | |
location().warning( | |
tr("Nonstandard wording in '\\%1' text for '%2' (" | |
"expected 'class', 'function', 'macro', '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::Sections Doc::granularity() const | |
{ | |
if (priv == 0 || priv->extra == 0) { | |
return DocPrivateExtra().granularity; | |
} | |
else { | |
return priv->extra->granularity; | |
} | |
} | |
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 QStringMultiMap &Doc::metaTagMap() const | |
{ | |
return priv && priv->extra ? priv->extra->metaMap : *null_QStringMultiMap(); | |
} | |
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); | |
QmlClassNode::qmlOnly = config.getBool(CONFIG_QMLONLY); | |
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(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.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, 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) { | |
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; | |
} | |
QT_END_NAMESPACE |