Merge branch 'master' of scm.dev.nokia.troll.no:qt/oslo-staging-1 into master-integration
[qt-netbsd.git] / tools / qdoc3 / doc.cpp
blob17a6efd0b681281f6aaf55514be25cfd200296be
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the tools applications of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
38 ** $QT_END_LICENSE$
40 ****************************************************************************/
42 #include "config.h"
43 #include "doc.h"
44 #include "codemarker.h"
45 #include "editdistance.h"
46 #include "openedlist.h"
47 #include "quoter.h"
48 #include "text.h"
49 #include "tokenizer.h"
50 #include <qdatetime.h>
51 #include <qdebug.h>
52 #include <qfile.h>
53 #include <qfileinfo.h>
54 #include <qhash.h>
55 #include <qtextstream.h>
56 #include <qregexp.h>
57 #include <ctype.h>
58 #include <limits.h>
60 QT_BEGIN_NAMESPACE
62 Q_GLOBAL_STATIC(QSet<QString>, null_Set_QString)
63 Q_GLOBAL_STATIC(QStringList, null_QStringList)
64 Q_GLOBAL_STATIC(QList<Text>, null_QList_Text)
65 Q_GLOBAL_STATIC(QStringMap, null_QStringMap)
67 struct Macro
69 QString defaultDef;
70 Location defaultDefLocation;
71 QStringMap otherDefs;
72 int numParams;
75 enum {
76 CMD_A, CMD_ABSTRACT, CMD_ANNOTATEDLIST, CMD_BADCODE,
77 CMD_BASENAME, CMD_BOLD, CMD_BRIEF, CMD_C, CMD_CAPTION,
78 CMD_CHAPTER, CMD_CODE, CMD_CODELINE, CMD_DOTS, CMD_ELSE,
79 CMD_ENDABSTRACT, CMD_ENDCHAPTER, CMD_ENDCODE,
80 CMD_ENDFOOTNOTE, CMD_ENDIF, CMD_ENDLEGALESE, CMD_ENDLINK,
81 CMD_ENDLIST, CMD_ENDOMIT, CMD_ENDPART, CMD_ENDQUOTATION,
82 CMD_ENDRAW, CMD_ENDSECTION1, CMD_ENDSECTION2,
83 CMD_ENDSECTION3, CMD_ENDSECTION4, CMD_ENDSIDEBAR,
84 CMD_ENDTABLE, CMD_EXPIRE, CMD_FOOTNOTE, CMD_GENERATELIST,
85 CMD_GRANULARITY, CMD_HEADER, CMD_I, CMD_IF, CMD_IMAGE,
86 CMD_INCLUDE, CMD_INLINEIMAGE, CMD_INDEX, CMD_KEYWORD,
87 CMD_L, CMD_LEGALESE, CMD_LINK, CMD_LIST, CMD_META,
88 CMD_NEWCODE, CMD_O, CMD_OLDCODE, CMD_OMIT, CMD_OMITVALUE,
89 CMD_OVERLOAD, CMD_PART, CMD_PRINTLINE, CMD_PRINTTO,
90 CMD_PRINTUNTIL, CMD_QUOTATION, CMD_QUOTEFILE,
91 CMD_QUOTEFROMFILE, CMD_QUOTEFUNCTION, CMD_RAW, CMD_ROW,
92 CMD_SA, CMD_SECTION1, CMD_SECTION2, CMD_SECTION3,
93 CMD_SECTION4, CMD_SIDEBAR, CMD_SINCELIST, CMD_SKIPLINE,
94 CMD_SKIPTO, CMD_SKIPUNTIL, CMD_SNIPPET, CMD_SUB, CMD_SUP,
95 CMD_TABLE, CMD_TABLEOFCONTENTS, CMD_TARGET, CMD_TT,
96 CMD_UNDERLINE, CMD_UNICODE, CMD_VALUE, CMD_WARNING,
97 #ifdef QDOC_QML
98 CMD_QML, CMD_ENDQML, CMD_CPP, CMD_ENDCPP, CMD_QMLTEXT,
99 CMD_ENDQMLTEXT, CMD_CPPTEXT, CMD_ENDCPPTEXT,
100 #endif
101 NOT_A_CMD
104 static struct {
105 const char *english;
106 int no;
107 QString *alias;
108 } cmds[] = {
109 { "a", CMD_A, 0 },
110 { "abstract", CMD_ABSTRACT, 0 },
111 { "annotatedlist", CMD_ANNOTATEDLIST, 0 },
112 { "badcode", CMD_BADCODE, 0 },
113 { "basename", CMD_BASENAME, 0 }, // ### don't document for now
114 { "bold", CMD_BOLD, 0 },
115 { "brief", CMD_BRIEF, 0 },
116 { "c", CMD_C, 0 },
117 { "caption", CMD_CAPTION, 0 },
118 { "chapter", CMD_CHAPTER, 0 },
119 { "code", CMD_CODE, 0 },
120 { "codeline", CMD_CODELINE, 0},
121 { "dots", CMD_DOTS, 0 },
122 { "else", CMD_ELSE, 0 },
123 { "endabstract", CMD_ENDABSTRACT, 0 },
124 { "endchapter", CMD_ENDCHAPTER, 0 },
125 { "endcode", CMD_ENDCODE, 0 },
126 { "endfootnote", CMD_ENDFOOTNOTE, 0 },
127 { "endif", CMD_ENDIF, 0 },
128 { "endlegalese", CMD_ENDLEGALESE, 0 },
129 { "endlink", CMD_ENDLINK, 0 },
130 { "endlist", CMD_ENDLIST, 0 },
131 { "endomit", CMD_ENDOMIT, 0 },
132 { "endpart", CMD_ENDPART, 0 },
133 { "endquotation", CMD_ENDQUOTATION, 0 },
134 { "endraw", CMD_ENDRAW, 0 },
135 { "endsection1", CMD_ENDSECTION1, 0 }, // ### don't document for now
136 { "endsection2", CMD_ENDSECTION2, 0 }, // ### don't document for now
137 { "endsection3", CMD_ENDSECTION3, 0 }, // ### don't document for now
138 { "endsection4", CMD_ENDSECTION4, 0 }, // ### don't document for now
139 { "endsidebar", CMD_ENDSIDEBAR, 0 },
140 { "endtable", CMD_ENDTABLE, 0 },
141 { "expire", CMD_EXPIRE, 0 },
142 { "footnote", CMD_FOOTNOTE, 0 },
143 { "generatelist", CMD_GENERATELIST, 0 },
144 { "granularity", CMD_GRANULARITY, 0 }, // ### don't document for now
145 { "header", CMD_HEADER, 0 },
146 { "i", CMD_I, 0 },
147 { "if", CMD_IF, 0 },
148 { "image", CMD_IMAGE, 0 },
149 { "include", CMD_INCLUDE, 0 },
150 { "inlineimage", CMD_INLINEIMAGE, 0 },
151 { "index", CMD_INDEX, 0 }, // ### don't document for now
152 { "keyword", CMD_KEYWORD, 0 },
153 { "l", CMD_L, 0 },
154 { "legalese", CMD_LEGALESE, 0 },
155 { "link", CMD_LINK, 0 },
156 { "list", CMD_LIST, 0 },
157 { "meta", CMD_META, 0 },
158 { "newcode", CMD_NEWCODE, 0 },
159 { "o", CMD_O, 0 },
160 { "oldcode", CMD_OLDCODE, 0 },
161 { "omit", CMD_OMIT, 0 },
162 { "omitvalue", CMD_OMITVALUE, 0 },
163 { "overload", CMD_OVERLOAD, 0 },
164 { "part", CMD_PART, 0 },
165 { "printline", CMD_PRINTLINE, 0 },
166 { "printto", CMD_PRINTTO, 0 },
167 { "printuntil", CMD_PRINTUNTIL, 0 },
168 { "quotation", CMD_QUOTATION, 0 },
169 { "quotefile", CMD_QUOTEFILE, 0 },
170 { "quotefromfile", CMD_QUOTEFROMFILE, 0 },
171 { "quotefunction", CMD_QUOTEFUNCTION, 0 }, // ### don't document for now
172 { "raw", CMD_RAW, 0 },
173 { "row", CMD_ROW, 0 },
174 { "sa", CMD_SA, 0 },
175 { "section1", CMD_SECTION1, 0 },
176 { "section2", CMD_SECTION2, 0 },
177 { "section3", CMD_SECTION3, 0 },
178 { "section4", CMD_SECTION4, 0 },
179 { "sidebar", CMD_SIDEBAR, 0 }, // ### don't document for now
180 { "sincelist", CMD_SINCELIST, 0 },
181 { "skipline", CMD_SKIPLINE, 0 },
182 { "skipto", CMD_SKIPTO, 0 },
183 { "skipuntil", CMD_SKIPUNTIL, 0 },
184 { "snippet", CMD_SNIPPET, 0 },
185 { "sub", CMD_SUB, 0 },
186 { "sup", CMD_SUP, 0 },
187 { "table", CMD_TABLE, 0 },
188 { "tableofcontents", CMD_TABLEOFCONTENTS, 0 },
189 { "target", CMD_TARGET, 0 },
190 { "tt", CMD_TT, 0 },
191 { "underline", CMD_UNDERLINE, 0 },
192 { "unicode", CMD_UNICODE, 0 },
193 { "value", CMD_VALUE, 0 },
194 { "warning", CMD_WARNING, 0 },
195 #ifdef QDOC_QML
196 { "qml", CMD_QML, 0 },
197 { "endqml", CMD_ENDQML, 0 },
198 { "cpp", CMD_CPP, 0 },
199 { "endcpp", CMD_ENDCPP, 0 },
200 { "qmltext", CMD_QMLTEXT, 0 },
201 { "endqmltext", CMD_ENDQMLTEXT, 0 },
202 { "cpptext", CMD_CPPTEXT, 0 },
203 { "endcpptext", CMD_ENDCPPTEXT, 0 },
204 #endif
205 { 0, 0, 0 }
208 typedef QHash<QString, int> QHash_QString_int;
209 typedef QHash<QString, Macro> QHash_QString_Macro;
211 Q_GLOBAL_STATIC(QStringMap, aliasMap)
212 Q_GLOBAL_STATIC(QHash_QString_int, cmdHash)
213 Q_GLOBAL_STATIC(QHash_QString_Macro, macroHash)
215 class DocPrivateExtra
217 public:
218 QString baseName;
219 Doc::SectioningUnit granularity;
220 Doc::SectioningUnit sectioningUnit; // ###
221 QList<Atom*> tableOfContents;
222 QList<int> tableOfContentsLevels;
223 QList<Atom*> keywords;
224 QList<Atom*> targets;
225 QStringMap metaMap;
227 DocPrivateExtra()
228 : granularity(Doc::Part) { }
231 struct Shared // ### get rid of
233 Shared()
234 : count(1) { }
235 void ref() { ++count; }
236 bool deref() { return (--count == 0); }
238 int count;
241 static QString cleanLink(const QString &link)
243 int colonPos = link.indexOf(':');
244 if ((colonPos == -1) ||
245 (!link.startsWith("file:") && !link.startsWith("mailto:")))
246 return link;
247 return link.mid(colonPos + 1).simplified();
250 class DocPrivate : public Shared
252 public:
253 DocPrivate(const Location& start = Location::null,
254 const Location& end = Location::null,
255 const QString& source = "");
256 ~DocPrivate();
258 void addAlso(const Text& also);
259 void constructExtra();
260 bool isEnumDocSimplifiable() const;
262 // ### move some of this in DocPrivateExtra
263 Location start_loc;
264 Location end_loc;
265 QString src;
266 Text text;
267 QSet<QString> params;
268 QList<Text> alsoList;
269 QStringList enumItemList;
270 QStringList omitEnumItemList;
271 QSet<QString> metacommandsUsed;
272 QCommandMap metaCommandMap;
273 bool hasLegalese : 1;
274 bool hasSectioningUnits : 1;
275 DocPrivateExtra *extra;
278 DocPrivate::DocPrivate(const Location& start,
279 const Location& end,
280 const QString& source)
281 : start_loc(start),
282 end_loc(end),
283 src(source),
284 hasLegalese(false),
285 hasSectioningUnits(false),
286 extra(0)
288 // nothing.
291 DocPrivate::~DocPrivate()
293 delete extra;
296 void DocPrivate::addAlso(const Text& also)
298 alsoList.append(also);
301 void DocPrivate::constructExtra()
303 if (extra == 0)
304 extra = new DocPrivateExtra;
307 bool DocPrivate::isEnumDocSimplifiable() const
309 bool justMetColon = false;
310 int numValueTables = 0;
312 const Atom *atom = text.firstAtom();
313 while (atom) {
314 if (atom->type() == Atom::AutoLink || atom->type() == Atom::String) {
315 justMetColon = atom->string().endsWith(":");
317 else if ((atom->type() == Atom::ListLeft) &&
318 (atom->string() == ATOM_LIST_VALUE)) {
319 if (justMetColon || numValueTables > 0)
320 return false;
321 ++numValueTables;
323 atom = atom->next();
325 return true;
328 class DocParser
330 public:
331 void parse(const QString &source,
332 DocPrivate *docPrivate,
333 const QSet<QString> &metaCommandSet);
335 static int endCmdFor(int cmd);
336 static QString cmdName(int cmd);
337 static QString endCmdName(int cmd);
338 static QString untabifyEtc(const QString& str);
339 static int indentLevel(const QString& str);
340 static QString unindent(int level, const QString& str);
341 static QString slashed(const QString& str);
343 static int tabSize;
344 static QStringList exampleFiles;
345 static QStringList exampleDirs;
346 static QStringList sourceFiles;
347 static QStringList sourceDirs;
348 static bool quoting;
350 private:
351 Location& location();
352 QString detailsUnknownCommand(const QSet<QString>& metaCommandSet,
353 const QString& str);
354 void checkExpiry(const QString& date);
355 void insertBaseName(const QString &baseName);
356 void insertTarget(const QString& target, bool keyword);
357 void include(const QString& fileName);
358 void startFormat(const QString& format, int cmd);
359 bool openCommand(int cmd);
360 bool closeCommand(int endCmd);
361 void startSection(Doc::SectioningUnit unit, int cmd);
362 void endSection(int unit, int endCmd);
363 void parseAlso();
364 void append(Atom::Type type, const QString& string = "");
365 void appendChar(QChar ch);
366 void appendWord(const QString &word);
367 void appendToCode(const QString &code);
368 void startNewPara();
369 void enterPara(Atom::Type leftType = Atom::ParaLeft,
370 Atom::Type rightType = Atom::ParaRight,
371 const QString& string = "");
372 void leavePara();
373 void leaveValue();
374 void leaveValueList();
375 void leaveTableRow();
376 CodeMarker *quoteFromFile();
377 void expandMacro(const QString& name, const QString& def, int numParams);
378 Doc::SectioningUnit getSectioningUnit();
379 QString getArgument(bool verbatim = false);
380 QString getOptionalArgument();
381 QString getRestOfLine();
382 QString getMetaCommandArgument(const QString &cmdStr);
383 QString getUntilEnd(int cmd);
384 QString getCode(int cmd, CodeMarker *marker);
385 QString getUnmarkedCode(int cmd);
387 bool isBlankLine();
388 bool isLeftBraceAhead();
389 void skipSpacesOnLine();
390 void skipSpacesOrOneEndl();
391 void skipAllSpaces();
392 void skipToNextPreprocessorCommand();
394 QStack<int> openedInputs;
396 QString in;
397 int pos;
398 int len;
399 Location cachedLoc;
400 int cachedPos;
402 DocPrivate *priv;
403 enum ParaState { OutsidePara, InsideSingleLinePara, InsideMultiLinePara };
404 ParaState paraState;
405 bool inTableHeader;
406 bool inTableRow;
407 bool inTableItem;
408 bool indexStartedPara; // ### rename
409 Atom::Type pendingParaLeftType;
410 Atom::Type pendingParaRightType;
411 QString pendingParaString;
413 int braceDepth;
414 int minIndent;
415 Doc::SectioningUnit currentSectioningUnit;
416 QMap<QString, Location> targetMap;
417 QMap<int, QString> pendingFormats;
418 QStack<int> openedCommands;
419 QStack<OpenedList> openedLists;
420 Quoter quoter;
423 int DocParser::tabSize;
424 QStringList DocParser::exampleFiles;
425 QStringList DocParser::exampleDirs;
426 QStringList DocParser::sourceFiles;
427 QStringList DocParser::sourceDirs;
428 bool DocParser::quoting;
431 Parse the \a source string to build a Text data structure
432 in \a docPrivate. The Text data structure is a linked list
433 of Atoms.
435 \a metaCommandSet is the set of metacommands that may be
436 found in \a source. These metacommands are not markup text
437 commands. They are topic commands and related metacommands.
439 void DocParser::parse(const QString& source,
440 DocPrivate *docPrivate,
441 const QSet<QString>& metaCommandSet)
443 in = source;
444 pos = 0;
445 len = in.length();
446 cachedLoc = docPrivate->start_loc;
447 cachedPos = 0;
448 priv = docPrivate;
449 priv->text << Atom::Nop;
451 paraState = OutsidePara;
452 inTableHeader = false;
453 inTableRow = false;
454 inTableItem = false;
455 indexStartedPara = false;
456 pendingParaLeftType = Atom::Nop;
457 pendingParaRightType = Atom::Nop;
459 braceDepth = 0;
460 minIndent = INT_MAX;
461 currentSectioningUnit = Doc::Book;
462 openedCommands.push(CMD_OMIT);
463 quoter.reset();
465 CodeMarker *marker = 0;
466 Atom *currentLinkAtom = 0;
467 QString x;
468 QStack<bool> preprocessorSkipping;
469 int numPreprocessorSkipping = 0;
471 while (pos < len) {
472 QChar ch = in.at(pos);
474 switch (ch.unicode()) {
475 case '\\':
477 QString cmdStr;
478 pos++;
479 while (pos < len) {
480 ch = in.at(pos);
481 if (ch.isLetterOrNumber()) {
482 cmdStr += ch;
483 pos++;
485 else {
486 break;
489 if (cmdStr.isEmpty()) {
490 if (pos < len) {
491 enterPara();
492 if (in.at(pos).isSpace()) {
493 skipAllSpaces();
494 appendChar(QLatin1Char(' '));
496 else {
497 appendChar(in.at(pos++));
501 else {
502 int cmd = cmdHash()->value(cmdStr,NOT_A_CMD);
503 switch (cmd) {
504 case CMD_A:
505 enterPara();
506 x = getArgument();
507 append(Atom::FormattingLeft,ATOM_FORMATTING_PARAMETER);
508 append(Atom::String, x);
509 append(Atom::FormattingRight,ATOM_FORMATTING_PARAMETER);
510 priv->params.insert(x);
511 break;
512 case CMD_ABSTRACT:
513 if (openCommand(cmd)) {
514 leavePara();
515 append(Atom::AbstractLeft);
517 break;
518 case CMD_BADCODE:
519 leavePara();
520 append(Atom::CodeBad,getCode(CMD_BADCODE, marker));
521 break;
522 case CMD_BASENAME:
523 leavePara();
524 insertBaseName(getArgument());
525 break;
526 case CMD_BOLD:
527 startFormat(ATOM_FORMATTING_BOLD, cmd);
528 break;
529 case CMD_BRIEF:
530 leavePara();
531 enterPara(Atom::BriefLeft, Atom::BriefRight);
532 break;
533 case CMD_C:
534 enterPara();
535 x = untabifyEtc(getArgument(true));
536 marker = CodeMarker::markerForCode(x);
537 append(Atom::C, marker->markedUpCode(x, 0, ""));
538 break;
539 case CMD_CAPTION:
540 leavePara();
541 /* ... */
542 break;
543 case CMD_CHAPTER:
544 startSection(Doc::Chapter, cmd);
545 break;
546 case CMD_CODE:
547 leavePara();
548 append(Atom::Code, getCode(CMD_CODE, marker));
549 break;
550 #ifdef QDOC_QML
551 case CMD_QML:
552 leavePara();
553 append(Atom::Qml, getCode(CMD_QML, marker));
554 break;
555 case CMD_QMLTEXT:
556 append(Atom::QmlText);
557 break;
558 #endif
559 case CMD_CODELINE:
561 if (!quoting) {
562 if (priv->text.lastAtom()->type() == Atom::Code
563 && priv->text.lastAtom()->string().endsWith("\n\n"))
564 priv->text.lastAtom()->chopString();
565 appendToCode("\n");
567 else {
568 append(Atom::CodeQuoteCommand, cmdStr);
569 append(Atom::CodeQuoteArgument, " ");
572 break;
573 case CMD_DOTS:
575 if (!quoting) {
576 if (priv->text.lastAtom()->type() == Atom::Code
577 && priv->text.lastAtom()->string().endsWith("\n\n"))
578 priv->text.lastAtom()->chopString();
580 QString arg = getOptionalArgument();
581 int indent = 4;
582 if (!arg.isEmpty())
583 indent = arg.toInt();
584 for (int i = 0; i < indent; ++i)
585 appendToCode(" ");
586 appendToCode("...\n");
588 else {
589 append(Atom::CodeQuoteCommand, cmdStr);
590 QString arg = getOptionalArgument();
591 if (arg.isEmpty())
592 arg = "4";
593 append(Atom::CodeQuoteArgument, arg);
596 break;
597 case CMD_ELSE:
598 if (preprocessorSkipping.size() > 0) {
599 if (preprocessorSkipping.top()) {
600 --numPreprocessorSkipping;
602 else {
603 ++numPreprocessorSkipping;
605 preprocessorSkipping.top() = !preprocessorSkipping.top();
606 (void)getRestOfLine(); // ### should ensure that it's empty
607 if (numPreprocessorSkipping)
608 skipToNextPreprocessorCommand();
610 else {
611 location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ELSE)));
613 break;
614 case CMD_ENDABSTRACT:
615 if (closeCommand(cmd)) {
616 leavePara();
617 append(Atom::AbstractRight);
619 break;
620 case CMD_ENDCHAPTER:
621 endSection(0, cmd);
622 break;
623 case CMD_ENDCODE:
624 closeCommand(cmd);
625 break;
626 #ifdef QDOC_QML
627 case CMD_ENDQML:
628 closeCommand(cmd);
629 break;
630 case CMD_ENDQMLTEXT:
631 append(Atom::EndQmlText);
632 break;
633 #endif
634 case CMD_ENDFOOTNOTE:
635 if (closeCommand(cmd)) {
636 leavePara();
637 append(Atom::FootnoteRight);
638 paraState = InsideMultiLinePara; // ###
640 break;
641 case CMD_ENDIF:
642 if (preprocessorSkipping.count() > 0) {
643 if (preprocessorSkipping.pop())
644 --numPreprocessorSkipping;
645 (void)getRestOfLine(); // ### should ensure that it's empty
646 if (numPreprocessorSkipping)
647 skipToNextPreprocessorCommand();
649 else {
650 location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ENDIF)));
652 break;
653 case CMD_ENDLEGALESE:
654 if (closeCommand(cmd)) {
655 leavePara();
656 append(Atom::LegaleseRight);
658 break;
659 case CMD_ENDLINK:
660 if (closeCommand(cmd)) {
661 if (priv->text.lastAtom()->type() == Atom::String
662 && priv->text.lastAtom()->string().endsWith(" "))
663 priv->text.lastAtom()->chopString();
664 append(Atom::FormattingRight, ATOM_FORMATTING_LINK);
666 break;
667 case CMD_ENDLIST:
668 if (closeCommand(cmd)) {
669 leavePara();
670 if (openedLists.top().isStarted()) {
671 append(Atom::ListItemRight,
672 openedLists.top().styleString());
673 append(Atom::ListRight,
674 openedLists.top().styleString());
676 openedLists.pop();
678 break;
679 case CMD_ENDOMIT:
680 closeCommand(cmd);
681 break;
682 case CMD_ENDPART:
683 endSection(-1, cmd);
684 break;
685 case CMD_ENDQUOTATION:
686 if (closeCommand(cmd)) {
687 leavePara();
688 append(Atom::QuotationRight);
690 break;
691 case CMD_ENDRAW:
692 location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ENDRAW)));
693 break;
694 case CMD_ENDSECTION1:
695 endSection(1, cmd);
696 break;
697 case CMD_ENDSECTION2:
698 endSection(2, cmd);
699 break;
700 case CMD_ENDSECTION3:
701 endSection(3, cmd);
702 break;
703 case CMD_ENDSECTION4:
704 endSection(4, cmd);
705 break;
706 case CMD_ENDSIDEBAR:
707 if (closeCommand(cmd)) {
708 leavePara();
709 append(Atom::SidebarRight);
711 break;
712 case CMD_ENDTABLE:
713 if (closeCommand(cmd)) {
714 leaveTableRow();
715 append(Atom::TableRight);
717 break;
718 case CMD_EXPIRE:
719 checkExpiry(getArgument());
720 break;
721 case CMD_FOOTNOTE:
722 if (openCommand(cmd)) {
723 enterPara();
724 append(Atom::FootnoteLeft);
725 paraState = OutsidePara; // ###
727 break;
728 case CMD_ANNOTATEDLIST:
729 append(Atom::AnnotatedList, getArgument());
730 break;
731 case CMD_SINCELIST:
732 append(Atom::SinceList, getArgument());
733 break;
734 case CMD_GENERATELIST:
735 append(Atom::GeneratedList, getArgument());
736 break;
737 case CMD_GRANULARITY:
738 priv->constructExtra();
739 priv->extra->granularity = getSectioningUnit();
740 break;
741 case CMD_HEADER:
742 if (openedCommands.top() == CMD_TABLE) {
743 leaveTableRow();
744 append(Atom::TableHeaderLeft);
745 inTableHeader = true;
747 else {
748 if (openedCommands.contains(CMD_TABLE)) {
749 location().warning(tr("Cannot use '\\%1' within '\\%2'")
750 .arg(cmdName(CMD_HEADER))
751 .arg(cmdName(openedCommands.top())));
753 else {
754 location().warning(tr("Cannot use '\\%1' outside of '\\%2'")
755 .arg(cmdName(CMD_HEADER))
756 .arg(cmdName(CMD_TABLE)));
759 break;
760 case CMD_I:
761 startFormat(ATOM_FORMATTING_ITALIC, cmd);
762 break;
763 case CMD_IF:
764 preprocessorSkipping.push(!Tokenizer::isTrue(getRestOfLine()));
765 if (preprocessorSkipping.top())
766 ++numPreprocessorSkipping;
767 if (numPreprocessorSkipping)
768 skipToNextPreprocessorCommand();
769 break;
770 case CMD_IMAGE:
771 leaveValueList();
772 append(Atom::Image, getArgument());
773 append(Atom::ImageText, getRestOfLine());
774 break;
775 case CMD_INCLUDE:
776 include(getArgument());
777 break;
778 case CMD_INLINEIMAGE:
779 enterPara();
780 append(Atom::InlineImage, getArgument());
781 append(Atom::ImageText, getRestOfLine());
782 append(Atom::String, " ");
783 break;
784 case CMD_INDEX:
785 if (paraState == OutsidePara) {
786 enterPara();
787 indexStartedPara = true;
789 else {
790 const Atom *last = priv->text.lastAtom();
791 if (indexStartedPara &&
792 (last->type() != Atom::FormattingRight ||
793 last->string() != ATOM_FORMATTING_INDEX))
794 indexStartedPara = false;
796 startFormat(ATOM_FORMATTING_INDEX, cmd);
797 break;
798 case CMD_KEYWORD:
799 insertTarget(getRestOfLine(),true);
800 break;
801 case CMD_L:
802 enterPara();
803 if (isLeftBraceAhead()) {
804 x = getArgument();
805 append(Atom::Link, x);
806 if (isLeftBraceAhead()) {
807 currentLinkAtom = priv->text.lastAtom();
808 startFormat(ATOM_FORMATTING_LINK, cmd);
810 else {
811 append(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
812 append(Atom::String, cleanLink(x));
813 append(Atom::FormattingRight, ATOM_FORMATTING_LINK);
816 else {
817 x = getArgument();
818 append(Atom::Link, x);
819 append(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
820 append(Atom::String, cleanLink(x));
821 append(Atom::FormattingRight, ATOM_FORMATTING_LINK);
823 break;
824 case CMD_LEGALESE:
825 leavePara();
826 if (openCommand(cmd))
827 append(Atom::LegaleseLeft);
828 docPrivate->hasLegalese = true;
829 break;
830 case CMD_LINK:
831 if (openCommand(cmd)) {
832 enterPara();
833 x = getArgument();
834 append(Atom::Link, x);
835 append(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
836 skipSpacesOrOneEndl();
838 break;
839 case CMD_LIST:
840 if (openCommand(cmd)) {
841 leavePara();
842 openedLists.push(OpenedList(location(),
843 getOptionalArgument()));
845 break;
846 case CMD_META:
847 priv->constructExtra();
848 x = getArgument();
849 priv->extra->metaMap.insert(x, getRestOfLine());
850 break;
851 case CMD_NEWCODE:
852 location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_NEWCODE)));
853 break;
854 case CMD_O:
855 leavePara();
856 if (openedCommands.top() == CMD_LIST) {
857 if (openedLists.top().isStarted()) {
858 append(Atom::ListItemRight,
859 openedLists.top().styleString());
861 else {
862 append(Atom::ListLeft,
863 openedLists.top().styleString());
865 openedLists.top().next();
866 append(Atom::ListItemNumber,
867 openedLists.top().numberString());
868 append(Atom::ListItemLeft,
869 openedLists.top().styleString());
870 enterPara();
872 else if (openedCommands.top() == CMD_TABLE) {
873 x = "1,1";
874 if (isLeftBraceAhead())
875 x = getArgument();
877 if (!inTableHeader && !inTableRow) {
878 location().warning(tr("Missing '\\%1' or '\\%1' before '\\%3'")
879 .arg(cmdName(CMD_HEADER))
880 .arg(cmdName(CMD_ROW))
881 .arg(cmdName(CMD_O)));
882 append(Atom::TableRowLeft);
883 inTableRow = true;
885 else if (inTableItem) {
886 append(Atom::TableItemRight);
887 inTableItem = false;
890 append(Atom::TableItemLeft, x);
891 inTableItem = true;
893 else {
894 location().warning(tr("Command '\\%1' outside of '\\%2' and '\\%3'")
895 .arg(cmdName(cmd))
896 .arg(cmdName(CMD_LIST))
897 .arg(cmdName(CMD_TABLE)));
899 break;
900 case CMD_OLDCODE:
901 leavePara();
902 append(Atom::CodeOld, getCode(CMD_OLDCODE, marker));
903 append(Atom::CodeNew, getCode(CMD_NEWCODE, marker));
904 break;
905 case CMD_OMIT:
906 getUntilEnd(cmd);
907 break;
908 case CMD_OMITVALUE:
909 x = getArgument();
910 if (!priv->enumItemList.contains(x))
911 priv->enumItemList.append(x);
912 if (!priv->omitEnumItemList.contains(x))
913 priv->omitEnumItemList.append(x);
914 break;
915 case CMD_PART:
916 startSection(Doc::Part, cmd);
917 break;
918 case CMD_PRINTLINE:
919 leavePara();
920 if (!quoting)
921 appendToCode(quoter.quoteLine(location(), cmdStr,
922 getRestOfLine()));
923 else {
924 append(Atom::CodeQuoteCommand, cmdStr);
925 append(Atom::CodeQuoteArgument, getRestOfLine());
927 break;
928 case CMD_PRINTTO:
929 leavePara();
930 if (!quoting)
931 appendToCode(quoter.quoteTo(location(), cmdStr,
932 getRestOfLine()));
933 else {
934 append(Atom::CodeQuoteCommand, cmdStr);
935 append(Atom::CodeQuoteArgument, getRestOfLine());
937 break;
938 case CMD_PRINTUNTIL:
939 leavePara();
940 if (!quoting)
941 appendToCode(quoter.quoteUntil(location(), cmdStr,
942 getRestOfLine()));
943 else {
944 append(Atom::CodeQuoteCommand, cmdStr);
945 append(Atom::CodeQuoteArgument, getRestOfLine());
947 break;
948 case CMD_QUOTATION:
949 if (openCommand(cmd)) {
950 leavePara();
951 append(Atom::QuotationLeft);
953 break;
954 case CMD_QUOTEFILE:
956 leavePara();
957 QString fileName = getArgument();
958 Doc::quoteFromFile(location(), quoter, fileName);
959 if (!quoting) {
960 append(Atom::Code,
961 quoter.quoteTo(location(), cmdStr, ""));
962 quoter.reset();
964 else {
965 append(Atom::CodeQuoteCommand, cmdStr);
966 append(Atom::CodeQuoteArgument, fileName);
968 break;
970 case CMD_QUOTEFROMFILE:
971 leavePara();
972 if (!quoting)
973 quoteFromFile();
974 else {
975 append(Atom::CodeQuoteCommand, cmdStr);
976 append(Atom::CodeQuoteArgument, getArgument());
978 break;
979 case CMD_QUOTEFUNCTION:
980 leavePara();
981 marker = quoteFromFile();
982 x = getRestOfLine();
983 if (!quoting) {
984 quoter.quoteTo(location(), cmdStr,
985 slashed(marker->functionBeginRegExp(x)));
986 append(Atom::Code,
987 quoter.quoteUntil(location(), cmdStr,
988 slashed(marker->functionEndRegExp(x))));
989 quoter.reset();
991 else {
992 append(Atom::CodeQuoteCommand, cmdStr);
993 append(Atom::CodeQuoteArgument, slashed(marker->functionEndRegExp(x)));
995 break;
996 case CMD_RAW:
997 leavePara();
998 x = getRestOfLine();
999 if (x.isEmpty())
1000 location().warning(tr("Missing format name after '\\%1")
1001 .arg(cmdName(CMD_RAW)));
1002 append(Atom::FormatIf, x);
1003 append(Atom::RawString, untabifyEtc(getUntilEnd(cmd)));
1004 append(Atom::FormatElse);
1005 append(Atom::FormatEndif);
1006 break;
1007 case CMD_ROW:
1008 if (openedCommands.top() == CMD_TABLE) {
1009 leaveTableRow();
1010 append(Atom::TableRowLeft);
1011 inTableRow = true;
1013 else {
1014 if (openedCommands.contains(CMD_TABLE)) {
1015 location().warning(tr("Cannot use '\\%1' within '\\%2'")
1016 .arg(cmdName(CMD_ROW))
1017 .arg(cmdName(openedCommands.top())));
1019 else {
1020 location().warning(tr("Cannot use '\\%1' outside of '\\%2'")
1021 .arg(cmdName(CMD_ROW))
1022 .arg(cmdName(CMD_TABLE)));
1025 break;
1026 case CMD_SA:
1027 parseAlso();
1028 break;
1029 case CMD_SECTION1:
1030 startSection(Doc::Section1, cmd);
1031 break;
1032 case CMD_SECTION2:
1033 startSection(Doc::Section2, cmd);
1034 break;
1035 case CMD_SECTION3:
1036 startSection(Doc::Section3, cmd);
1037 break;
1038 case CMD_SECTION4:
1039 startSection(Doc::Section4, cmd);
1040 break;
1041 case CMD_SIDEBAR:
1042 if (openCommand(cmd)) {
1043 leavePara();
1044 append(Atom::SidebarLeft);
1046 break;
1047 case CMD_SKIPLINE:
1048 leavePara();
1049 if (!quoting)
1050 quoter.quoteLine(location(),
1051 cmdStr,
1052 getRestOfLine());
1053 else {
1054 append(Atom::CodeQuoteCommand, cmdStr);
1055 append(Atom::CodeQuoteArgument, getRestOfLine());
1057 break;
1058 case CMD_SKIPTO:
1059 leavePara();
1060 if (!quoting)
1061 quoter.quoteTo(location(),
1062 cmdStr,
1063 getRestOfLine());
1064 else {
1065 append(Atom::CodeQuoteCommand, cmdStr);
1066 append(Atom::CodeQuoteArgument, getRestOfLine());
1068 break;
1069 case CMD_SKIPUNTIL:
1070 leavePara();
1071 if (!quoting)
1072 quoter.quoteUntil(location(),
1073 cmdStr,
1074 getRestOfLine());
1075 else {
1076 append(Atom::CodeQuoteCommand, cmdStr);
1077 append(Atom::CodeQuoteArgument, getRestOfLine());
1079 break;
1080 case CMD_SNIPPET:
1081 leavePara();
1083 QString snippet = getArgument();
1084 QString identifier = getRestOfLine();
1085 if (quoting) {
1086 append(Atom::SnippetCommand, cmdStr);
1087 append(Atom::SnippetLocation, snippet);
1088 append(Atom::SnippetIdentifier, identifier);
1090 else {
1091 Doc::quoteFromFile(location(),quoter,snippet);
1092 appendToCode(quoter.quoteSnippet(location(),
1093 identifier));
1096 break;
1097 case CMD_SUB:
1098 startFormat(ATOM_FORMATTING_SUBSCRIPT, cmd);
1099 break;
1100 case CMD_SUP:
1101 startFormat(ATOM_FORMATTING_SUPERSCRIPT, cmd);
1102 break;
1103 case CMD_TABLE:
1104 x = getRestOfLine();
1105 if (openCommand(cmd)) {
1106 leavePara();
1107 append(Atom::TableLeft, x);
1108 inTableHeader = false;
1109 inTableRow = false;
1110 inTableItem = false;
1112 break;
1113 case CMD_TABLEOFCONTENTS:
1114 x = "1";
1115 if (isLeftBraceAhead())
1116 x = getArgument();
1117 x += ",";
1118 x += QString::number((int)getSectioningUnit());
1119 append(Atom::TableOfContents, x);
1120 break;
1121 case CMD_TARGET:
1122 insertTarget(getRestOfLine(),false);
1123 break;
1124 case CMD_TT:
1125 startFormat(ATOM_FORMATTING_TELETYPE, cmd);
1126 break;
1127 case CMD_UNDERLINE:
1128 startFormat(ATOM_FORMATTING_UNDERLINE, cmd);
1129 break;
1130 case CMD_UNICODE:
1131 enterPara();
1132 x = getArgument();
1134 bool ok;
1135 uint unicodeChar = x.toUInt(&ok, 0);
1136 if (!ok ||
1137 (unicodeChar == 0x0000) ||
1138 (unicodeChar > 0xFFFE)) {
1139 location().warning(tr("Invalid Unicode character '%1' specified "
1140 "with '%2'")
1141 .arg(x, cmdName(CMD_UNICODE)));
1143 else {
1144 append(Atom::String, QChar(unicodeChar));
1147 break;
1148 case CMD_VALUE:
1149 leaveValue();
1150 if (openedLists.top().style() == OpenedList::Value) {
1151 x = getArgument();
1152 if (!priv->enumItemList.contains(x))
1153 priv->enumItemList.append(x);
1155 openedLists.top().next();
1156 append(Atom::ListTagLeft, ATOM_LIST_VALUE);
1157 append(Atom::String, x);
1158 append(Atom::ListTagRight, ATOM_LIST_VALUE);
1159 append(Atom::ListItemLeft, ATOM_LIST_VALUE);
1161 skipSpacesOrOneEndl();
1162 if (isBlankLine())
1163 append(Atom::Nop);
1165 else {
1166 // ### problems
1168 break;
1169 case CMD_WARNING:
1170 startNewPara();
1171 append(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1172 append(Atom::String, "Warning:");
1173 append(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1174 append(Atom::String, " ");
1175 break;
1176 case CMD_OVERLOAD:
1177 priv->metacommandsUsed.insert(cmdStr);
1178 x.clear();
1179 if (!isBlankLine())
1180 x = getRestOfLine();
1181 if (!x.isEmpty()) {
1182 append(Atom::ParaLeft);
1183 append(Atom::String, "This function overloads ");
1184 append(Atom::AutoLink,x);
1185 append(Atom::String, ".");
1186 append(Atom::ParaRight);
1188 else {
1189 append(Atom::ParaLeft);
1190 append(Atom::String,"This is an overloaded function.");
1191 append(Atom::ParaRight);
1192 x = getMetaCommandArgument(cmdStr);
1194 priv->metaCommandMap[cmdStr].append(x);
1195 break;
1196 case NOT_A_CMD:
1197 if (metaCommandSet.contains(cmdStr)) {
1198 priv->metacommandsUsed.insert(cmdStr);
1199 QString xxx = getMetaCommandArgument(cmdStr);
1200 priv->metaCommandMap[cmdStr].append(xxx);
1202 else if (macroHash()->contains(cmdStr)) {
1203 const Macro &macro = macroHash()->value(cmdStr);
1204 int numPendingFi = 0;
1205 QStringMap::ConstIterator d;
1206 d = macro.otherDefs.begin();
1207 while (d != macro.otherDefs.end()) {
1208 append(Atom::FormatIf, d.key());
1209 expandMacro(cmdStr, *d, macro.numParams);
1210 ++d;
1212 if (d == macro.otherDefs.end()) {
1213 append(Atom::FormatEndif);
1215 else {
1216 append(Atom::FormatElse);
1217 numPendingFi++;
1220 while (numPendingFi-- > 0)
1221 append(Atom::FormatEndif);
1223 if (!macro.defaultDef.isEmpty()) {
1224 if (!macro.otherDefs.isEmpty()) {
1225 macro.defaultDefLocation.warning(
1226 tr("Macro cannot have both "
1227 "format-specific and qdoc- "
1228 "syntax definitions"));
1230 else {
1231 location().push(macro.defaultDefLocation.filePath());
1232 in.insert(pos, macro.defaultDef);
1233 len = in.length();
1234 openedInputs.push(pos + macro.defaultDef.length());
1238 else {
1239 location().warning(
1240 tr("Unknown command '\\%1'").arg(cmdStr),
1241 detailsUnknownCommand(metaCommandSet,cmdStr));
1242 enterPara();
1243 append(Atom::UnknownCommand, cmdStr);
1248 break;
1249 case '{':
1250 enterPara();
1251 appendChar('{');
1252 braceDepth++;
1253 pos++;
1254 break;
1255 case '}':
1257 braceDepth--;
1258 pos++;
1260 QMap<int, QString>::Iterator f =
1261 pendingFormats.find(braceDepth);
1262 if (f == pendingFormats.end()) {
1263 enterPara();
1264 appendChar('}');
1266 else {
1267 append(Atom::FormattingRight, *f);
1268 if (*f == ATOM_FORMATTING_INDEX) {
1269 if (indexStartedPara)
1270 skipAllSpaces();
1272 else if (*f == ATOM_FORMATTING_LINK) {
1273 // hack for C++ to support links like
1274 // \l{QString::}{count()}
1275 if (currentLinkAtom &&
1276 currentLinkAtom->string().endsWith("::")) {
1277 QString suffix = Text::subText(currentLinkAtom,
1278 priv->text.lastAtom()).toString();
1279 currentLinkAtom->appendString(suffix);
1281 currentLinkAtom = 0;
1283 pendingFormats.erase(f);
1286 break;
1287 default:
1289 bool newWord;
1290 switch (priv->text.lastAtom()->type()) {
1291 case Atom::ParaLeft:
1292 newWord = true;
1293 break;
1294 default:
1295 newWord = false;
1298 if (paraState == OutsidePara) {
1299 if (ch.isSpace()) {
1300 ++pos;
1301 newWord = false;
1303 else {
1304 enterPara();
1305 newWord = true;
1308 else {
1309 if (ch.isSpace()) {
1310 ++pos;
1311 if ((ch == '\n') &&
1312 (paraState == InsideSingleLinePara ||
1313 isBlankLine())) {
1314 leavePara();
1315 newWord = false;
1317 else {
1318 appendChar(' ');
1319 newWord = true;
1322 else {
1323 newWord = true;
1327 if (newWord) {
1328 int startPos = pos;
1329 int numInternalUppercase = 0;
1330 int numLowercase = 0;
1331 int numStrangeSymbols = 0;
1333 while (pos < len) {
1334 unsigned char latin1Ch = in.at(pos).toLatin1();
1335 if (islower(latin1Ch)) {
1336 ++numLowercase;
1337 ++pos;
1339 else if (isupper(latin1Ch)) {
1340 if (pos > startPos)
1341 ++numInternalUppercase;
1342 ++pos;
1344 else if (isdigit(latin1Ch)) {
1345 if (pos > startPos) {
1346 ++pos;
1348 else {
1349 break;
1352 else if (latin1Ch == '_' || latin1Ch == '@') {
1353 ++numStrangeSymbols;
1354 ++pos;
1356 else if (latin1Ch == ':' && pos < len - 1
1357 && in.at(pos + 1) == QLatin1Char(':')) {
1358 ++numStrangeSymbols;
1359 pos += 2;
1361 else if (latin1Ch == '(') {
1362 if (pos > startPos) {
1363 if (pos < len - 1 &&
1364 in.at(pos + 1) == QLatin1Char(')')) {
1365 ++numStrangeSymbols;
1366 pos += 2;
1367 break;
1369 else {
1370 // ### handle functions with signatures
1371 // and function calls
1372 break;
1375 else {
1376 break;
1379 else {
1380 break;
1384 if (pos == startPos) {
1385 if (!ch.isSpace()) {
1386 appendChar(ch);
1387 ++pos;
1390 else {
1391 QString word = in.mid(startPos, pos - startPos);
1392 // is word a C++ symbol or an English word?
1393 if ((numInternalUppercase >= 1 && numLowercase >= 2)
1394 || numStrangeSymbols >= 1) {
1395 append(Atom::AutoLink, word);
1397 else {
1398 appendWord(word);
1405 leaveValueList();
1407 // for compatibility
1408 if (openedCommands.top() == CMD_LEGALESE) {
1409 append(Atom::LegaleseRight);
1410 openedCommands.pop();
1413 if (openedCommands.top() != CMD_OMIT) {
1414 location().warning(tr("Missing '\\%1'").arg(endCmdName(openedCommands.top())));
1416 else if (preprocessorSkipping.count() > 0) {
1417 location().warning(tr("Missing '\\%1'").arg(cmdName(CMD_ENDIF)));
1420 while (currentSectioningUnit > Doc::Chapter) {
1421 int delta = currentSectioningUnit - priv->extra->sectioningUnit;
1422 append(Atom::SectionRight, QString::number(delta));
1423 currentSectioningUnit = Doc::SectioningUnit(int(currentSectioningUnit) - 1);
1426 if (priv->extra && priv->extra->granularity < priv->extra->sectioningUnit)
1427 priv->extra->granularity = priv->extra->sectioningUnit;
1428 priv->text.stripFirstAtom();
1431 Location &DocParser::location()
1433 while (!openedInputs.isEmpty() && openedInputs.top() <= pos) {
1434 cachedLoc.pop();
1435 cachedPos = openedInputs.pop();
1437 while (cachedPos < pos)
1438 cachedLoc.advance(in.at(cachedPos++));
1439 return cachedLoc;
1442 QString DocParser::detailsUnknownCommand(const QSet<QString> &metaCommandSet,
1443 const QString &str)
1445 QSet<QString> commandSet = metaCommandSet;
1446 int i = 0;
1447 while (cmds[i].english != 0) {
1448 commandSet.insert(*cmds[i].alias);
1449 i++;
1452 if (aliasMap()->contains(str))
1453 return tr("The command '\\%1' was renamed '\\%2' by the configuration"
1454 " file. Use the new name.")
1455 .arg(str).arg((*aliasMap())[str]);
1457 QString best = nearestName(str, commandSet);
1458 if (best.isEmpty())
1459 return QString();
1460 return tr("Maybe you meant '\\%1'?").arg(best);
1463 void DocParser::checkExpiry(const QString& date)
1465 QRegExp ymd("(\\d{4})(?:-(\\d{2})(?:-(\\d{2})))");
1467 if (ymd.exactMatch(date)) {
1468 int y = ymd.cap(1).toInt();
1469 int m = ymd.cap(2).toInt();
1470 int d = ymd.cap(3).toInt();
1472 if (m == 0)
1473 m = 1;
1474 if (d == 0)
1475 d = 1;
1476 QDate expiryDate(y, m, d);
1477 if (expiryDate.isValid()) {
1478 int days = expiryDate.daysTo(QDate::currentDate());
1479 if (days == 0) {
1480 location().warning(tr("Documentation expires today"));
1482 else if (days == 1) {
1483 location().warning(tr("Documentation expired yesterday"));
1485 else if (days >= 2) {
1486 location().warning(tr("Documentation expired %1 days ago")
1487 .arg(days));
1490 else {
1491 location().warning(tr("Date '%1' invalid").arg(date));
1494 else {
1495 location().warning(tr("Date '%1' not in YYYY-MM-DD format")
1496 .arg(date));
1500 void DocParser::insertBaseName(const QString &baseName)
1502 priv->constructExtra();
1503 if (currentSectioningUnit == priv->extra->sectioningUnit) {
1504 priv->extra->baseName = baseName;
1506 else {
1507 Atom *atom = priv->text.firstAtom();
1508 Atom *sectionLeft = 0;
1510 int delta = currentSectioningUnit - priv->extra->sectioningUnit;
1512 while (atom != 0) {
1513 if (atom->type() == Atom::SectionLeft &&
1514 atom->string().toInt() == delta)
1515 sectionLeft = atom;
1516 atom = atom->next();
1518 if (sectionLeft != 0)
1519 (void) new Atom(sectionLeft, Atom::BaseName, baseName);
1523 void DocParser::insertTarget(const QString &target, bool keyword)
1525 if (targetMap.contains(target)) {
1526 location().warning(tr("Duplicate target name '%1'").arg(target));
1527 targetMap[target].warning(tr("(The previous occurrence is here)"));
1529 else {
1530 targetMap.insert(target, location());
1531 append(Atom::Target, target);
1532 priv->constructExtra();
1533 if (keyword)
1534 priv->extra->keywords.append(priv->text.lastAtom());
1535 else
1536 priv->extra->targets.append(priv->text.lastAtom());
1540 void DocParser::include(const QString& fileName)
1542 if (location().depth() > 16)
1543 location().fatal(tr("Too many nested '\\%1's")
1544 .arg(cmdName(CMD_INCLUDE)));
1546 QString userFriendlyFilePath;
1547 // ### use current directory?
1548 QString filePath = Config::findFile(location(),
1549 sourceFiles,
1550 sourceDirs,
1551 fileName,
1552 userFriendlyFilePath);
1553 if (filePath.isEmpty()) {
1554 location().warning(tr("Cannot find leaf file '%1'").arg(fileName));
1556 else {
1557 QFile inFile(filePath);
1558 if (!inFile.open(QFile::ReadOnly)) {
1559 location().warning(tr("Cannot open leaf file '%1'")
1560 .arg(userFriendlyFilePath));
1562 else {
1563 location().push(userFriendlyFilePath);
1565 QTextStream inStream(&inFile);
1566 QString includedStuff = inStream.readAll();
1567 inFile.close();
1569 in.insert(pos, includedStuff);
1570 len = in.length();
1571 openedInputs.push(pos + includedStuff.length());
1576 void DocParser::startFormat(const QString& format, int cmd)
1578 enterPara();
1580 QMap<int, QString>::ConstIterator f = pendingFormats.begin();
1581 while (f != pendingFormats.end()) {
1582 if (*f == format) {
1583 location().warning(tr("Cannot nest '\\%1' commands")
1584 .arg(cmdName(cmd)));
1585 return;
1587 ++f;
1590 append(Atom::FormattingLeft, format);
1592 if (isLeftBraceAhead()) {
1593 skipSpacesOrOneEndl();
1594 pendingFormats.insert(braceDepth, format);
1595 ++braceDepth;
1596 ++pos;
1598 else {
1599 append(Atom::String, getArgument());
1600 append(Atom::FormattingRight, format);
1601 if (format == ATOM_FORMATTING_INDEX && indexStartedPara) {
1602 skipAllSpaces();
1603 indexStartedPara = false;
1608 bool DocParser::openCommand(int cmd)
1610 int outer = openedCommands.top();
1611 bool ok = true;
1613 if (cmd != CMD_LINK) {
1614 if (outer == CMD_LIST) {
1615 ok = (cmd == CMD_FOOTNOTE || cmd == CMD_LIST);
1617 else if (outer == CMD_ABSTRACT) {
1618 ok = (cmd == CMD_LIST ||
1619 cmd == CMD_QUOTATION ||
1620 cmd == CMD_TABLE);
1622 else if (outer == CMD_SIDEBAR) {
1623 ok = (cmd == CMD_LIST ||
1624 cmd == CMD_QUOTATION ||
1625 cmd == CMD_SIDEBAR);
1627 else if (outer == CMD_QUOTATION) {
1628 ok = (cmd == CMD_LIST);
1630 else if (outer == CMD_TABLE) {
1631 ok = (cmd == CMD_LIST ||
1632 cmd == CMD_FOOTNOTE ||
1633 cmd == CMD_QUOTATION);
1635 else if (outer == CMD_FOOTNOTE || outer == CMD_LINK) {
1636 ok = false;
1640 if (ok) {
1641 openedCommands.push(cmd);
1643 else {
1644 location().warning(tr("Cannot use '\\%1' within '\\%2'")
1645 .arg(cmdName(cmd)).arg(cmdName(outer)));
1647 return ok;
1650 bool DocParser::closeCommand(int endCmd)
1652 if (endCmdFor(openedCommands.top()) == endCmd && openedCommands.size() > 1) {
1653 openedCommands.pop();
1654 return true;
1656 else {
1657 bool contains = false;
1658 QStack<int> opened2 = openedCommands;
1659 while (opened2.size() > 1) {
1660 if (endCmdFor(opened2.top()) == endCmd) {
1661 contains = true;
1662 break;
1664 opened2.pop();
1667 if (contains) {
1668 while (endCmdFor(openedCommands.top()) != endCmd && openedCommands.size() > 1) {
1669 location().warning(tr("Missing '\\%1' before '\\%2'")
1670 .arg(endCmdName(openedCommands.top()))
1671 .arg(cmdName(endCmd)));
1672 openedCommands.pop();
1675 else {
1676 location().warning(tr("Unexpected '\\%1'")
1677 .arg(cmdName(endCmd)));
1679 return false;
1683 void DocParser::startSection(Doc::SectioningUnit unit, int cmd)
1685 leavePara();
1687 if (currentSectioningUnit == Doc::Book) {
1688 #if 0
1689 // mws didn't think this was necessary.
1690 if (unit > Doc::Section1)
1691 location().warning(tr("Unexpected '\\%1' without '\\%2'")
1692 .arg(cmdName(cmd))
1693 .arg(cmdName(CMD_SECTION1)));
1694 #endif
1695 currentSectioningUnit = (Doc::SectioningUnit) (unit - 1);
1696 priv->constructExtra();
1697 priv->extra->sectioningUnit = currentSectioningUnit;
1700 if (unit <= priv->extra->sectioningUnit) {
1701 location().warning(tr("Unexpected '\\%1' in this documentation")
1702 .arg(cmdName(cmd)));
1704 else if (unit - currentSectioningUnit > 1) {
1705 location().warning(tr("Unexpected '\\%1' at this point")
1706 .arg(cmdName(cmd)));
1708 else {
1709 if (currentSectioningUnit >= unit)
1710 endSection(unit, cmd);
1712 int delta = unit - priv->extra->sectioningUnit;
1713 append(Atom::SectionLeft, QString::number(delta));
1714 priv->constructExtra();
1715 priv->extra->tableOfContents.append(priv->text.lastAtom());
1716 priv->extra->tableOfContentsLevels.append(unit);
1717 enterPara(Atom::SectionHeadingLeft,
1718 Atom::SectionHeadingRight,
1719 QString::number(delta));
1720 currentSectioningUnit = unit;
1724 void DocParser::endSection(int unit, int endCmd)
1726 leavePara();
1728 if (unit < priv->extra->sectioningUnit) {
1729 location().warning(tr("Unexpected '\\%1' in this documentation")
1730 .arg(cmdName(endCmd)));
1732 else if (unit > currentSectioningUnit) {
1733 location().warning(tr("Unexpected '\\%1' at this point")
1734 .arg(cmdName(endCmd)));
1736 else {
1737 while (currentSectioningUnit >= unit) {
1738 int delta = currentSectioningUnit - priv->extra->sectioningUnit;
1739 append(Atom::SectionRight, QString::number(delta));
1740 currentSectioningUnit =
1741 (Doc::SectioningUnit) (currentSectioningUnit - 1);
1746 void DocParser::parseAlso()
1748 leavePara();
1749 skipSpacesOnLine();
1750 while (pos < len && in[pos] != '\n') {
1751 QString target;
1752 QString str;
1754 if (in[pos] == '{') {
1755 target = getArgument();
1756 skipSpacesOnLine();
1757 if (in[pos] == '{') {
1758 str = getArgument();
1760 // hack for C++ to support links like \l{QString::}{count()}
1761 if (target.endsWith("::"))
1762 target += str;
1764 else {
1765 str = target;
1767 #ifdef QDOC2_COMPAT
1769 else if (in[pos] == '\\' && in.mid(pos, 5) == "\\link") {
1770 pos += 6;
1771 target = getArgument();
1772 int endPos = in.indexOf("\\endlink", pos);
1773 if (endPos != -1) {
1774 str = in.mid(pos, endPos - pos).trimmed();
1775 pos = endPos + 8;
1777 #endif
1779 else {
1780 target = getArgument();
1781 str = cleanLink(target);
1784 Text also;
1785 also << Atom(Atom::Link, target)
1786 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1787 << str
1788 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1789 priv->addAlso(also);
1791 skipSpacesOnLine();
1792 if (pos < len && in[pos] == ',') {
1793 pos++;
1794 skipSpacesOrOneEndl();
1796 else if (in[pos] != '\n') {
1797 location().warning(tr("Missing comma in '\\%1'").arg(cmdName(CMD_SA)));
1802 void DocParser::append(Atom::Type type, const QString &string)
1804 Atom::Type lastType = priv->text.lastAtom()->type();
1805 #ifdef QDOC_QML
1806 if (((lastType == Atom::Code) || (lastType == Atom::Code)) &&
1807 #else
1808 if ((lastType == Atom::Code) &&
1809 #endif
1810 priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n")))
1811 priv->text.lastAtom()->chopString();
1812 priv->text << Atom(type, string);
1815 void DocParser::appendChar(QChar ch)
1817 if (priv->text.lastAtom()->type() != Atom::String)
1818 append(Atom::String);
1819 Atom *atom = priv->text.lastAtom();
1820 if (ch == QLatin1Char(' ')) {
1821 if (!atom->string().endsWith(QLatin1Char(' ')))
1822 atom->appendChar(QLatin1Char(' '));
1824 else
1825 atom->appendChar(ch);
1828 void DocParser::appendWord(const QString &word)
1830 if (priv->text.lastAtom()->type() != Atom::String) {
1831 append(Atom::String, word);
1833 else
1834 priv->text.lastAtom()->appendString(word);
1837 void DocParser::appendToCode(const QString& markedCode)
1839 Atom::Type lastType = priv->text.lastAtom()->type();
1840 #ifdef QDOC_QML
1841 if (lastType != Atom::Qml)
1842 append(Atom::Qml);
1843 #else
1844 if (lastType != Atom::Code)
1845 append(Atom::Code);
1846 #endif
1847 priv->text.lastAtom()->appendString(markedCode);
1850 void DocParser::startNewPara()
1852 leavePara();
1853 enterPara();
1856 void DocParser::enterPara(Atom::Type leftType,
1857 Atom::Type rightType,
1858 const QString& string)
1860 if (paraState == OutsidePara) {
1861 if (priv->text.lastAtom()->type() != Atom::ListItemLeft)
1862 leaveValueList();
1863 append(leftType, string);
1864 indexStartedPara = false;
1865 pendingParaLeftType = leftType;
1866 pendingParaRightType = rightType;
1867 pendingParaString = string;
1868 if (
1869 #if 0
1870 leftType == Atom::BriefLeft ||
1871 #endif
1872 leftType == Atom::SectionHeadingLeft) {
1873 paraState = InsideSingleLinePara;
1875 else {
1876 paraState = InsideMultiLinePara;
1878 skipSpacesOrOneEndl();
1882 void DocParser::leavePara()
1884 if (paraState != OutsidePara) {
1885 if (!pendingFormats.isEmpty()) {
1886 location().warning(tr("Missing '}'"));
1887 pendingFormats.clear();
1890 if (priv->text.lastAtom()->type() == pendingParaLeftType) {
1891 priv->text.stripLastAtom();
1893 else {
1894 if (priv->text.lastAtom()->type() == Atom::String &&
1895 priv->text.lastAtom()->string().endsWith(" ")) {
1896 priv->text.lastAtom()->chopString();
1898 append(pendingParaRightType, pendingParaString);
1900 paraState = OutsidePara;
1901 indexStartedPara = false;
1902 pendingParaRightType = Atom::Nop;
1903 pendingParaString = "";
1907 void DocParser::leaveValue()
1909 leavePara();
1910 if (openedLists.isEmpty()) {
1911 openedLists.push(OpenedList(OpenedList::Value));
1912 append(Atom::ListLeft, ATOM_LIST_VALUE);
1914 else {
1915 if (priv->text.lastAtom()->type() == Atom::Nop)
1916 priv->text.stripLastAtom();
1917 append(Atom::ListItemRight, ATOM_LIST_VALUE);
1921 void DocParser::leaveValueList()
1923 leavePara();
1924 if (!openedLists.isEmpty() &&
1925 (openedLists.top().style() == OpenedList::Value)) {
1926 if (priv->text.lastAtom()->type() == Atom::Nop)
1927 priv->text.stripLastAtom();
1928 append(Atom::ListItemRight, ATOM_LIST_VALUE);
1929 append(Atom::ListRight, ATOM_LIST_VALUE);
1930 openedLists.pop();
1934 void DocParser::leaveTableRow()
1936 if (inTableItem) {
1937 leavePara();
1938 append(Atom::TableItemRight);
1939 inTableItem = false;
1941 if (inTableHeader) {
1942 append(Atom::TableHeaderRight);
1943 inTableHeader = false;
1945 if (inTableRow) {
1946 append(Atom::TableRowRight);
1947 inTableRow = false;
1951 CodeMarker *DocParser::quoteFromFile()
1953 return Doc::quoteFromFile(location(), quoter, getArgument());
1956 void DocParser::expandMacro(const QString &name,
1957 const QString &def,
1958 int numParams)
1960 if (numParams == 0) {
1961 append(Atom::RawString, def);
1963 else {
1964 QStringList args;
1965 QString rawString;
1967 for (int i = 0; i < numParams; i++) {
1968 if (numParams == 1 || isLeftBraceAhead()) {
1969 args << getArgument(true);
1971 else {
1972 location().warning(tr("Macro '\\%1' invoked with too few"
1973 " arguments (expected %2, got %3)")
1974 .arg(name).arg(numParams).arg(i));
1975 break;
1979 int j = 0;
1980 while (j < def.size()) {
1981 int paramNo;
1982 if ((def[j] == '\\') && (j < def.size() - 1) &&
1983 ((paramNo = def[j + 1].digitValue()) >= 1) &&
1984 (paramNo <= numParams)) {
1985 if (!rawString.isEmpty()) {
1986 append(Atom::RawString, rawString);
1987 rawString = "";
1989 append(Atom::String, args[paramNo - 1]);
1990 j += 2;
1992 else {
1993 rawString += def[j++];
1996 if (!rawString.isEmpty())
1997 append(Atom::RawString, rawString);
2001 Doc::SectioningUnit DocParser::getSectioningUnit()
2003 QString name = getOptionalArgument();
2005 if (name == "part") {
2006 return Doc::Part;
2008 else if (name == "chapter") {
2009 return Doc::Chapter;
2011 else if (name == "section1") {
2012 return Doc::Section1;
2014 else if (name == "section2") {
2015 return Doc::Section2;
2017 else if (name == "section3") {
2018 return Doc::Section3;
2020 else if (name == "section4") {
2021 return Doc::Section4;
2023 else if (name.isEmpty()) {
2024 return Doc::Section4;
2026 else {
2027 location().warning(tr("Invalid sectioning unit '%1'").arg(name));
2028 return Doc::Book;
2032 QString DocParser::getArgument(bool verbatim)
2034 QString arg;
2035 int delimDepth = 0;
2037 skipSpacesOrOneEndl();
2039 int startPos = pos;
2042 Typically, an argument ends at the next white-space. However,
2043 braces can be used to group words:
2045 {a few words}
2047 Also, opening and closing parentheses have to match. Thus,
2049 printf("%d\n", x)
2051 is an argument too, although it contains spaces. Finally,
2052 trailing punctuation is not included in an argument, nor is 's.
2054 if (pos < (int) in.length() && in[pos] == '{') {
2055 pos++;
2056 while (pos < (int) in.length() && delimDepth >= 0) {
2057 switch (in[pos].unicode()) {
2058 case '{':
2059 delimDepth++;
2060 arg += "{";
2061 pos++;
2062 break;
2063 case '}':
2064 delimDepth--;
2065 if (delimDepth >= 0)
2066 arg += "}";
2067 pos++;
2068 break;
2069 case '\\':
2070 if (verbatim) {
2071 arg += in[pos];
2072 pos++;
2074 else {
2075 pos++;
2076 if (pos < (int) in.length()) {
2077 if (in[pos].isLetterOrNumber())
2078 break;
2079 arg += in[pos];
2080 if (in[pos].isSpace()) {
2081 skipAllSpaces();
2083 else {
2084 pos++;
2088 break;
2089 default:
2090 arg += in[pos];
2091 pos++;
2094 if (delimDepth > 0)
2095 location().warning(tr("Missing '}'"));
2097 else {
2098 while (pos < in.length() &&
2099 ((delimDepth > 0) ||
2100 ((delimDepth == 0) &&
2101 !in[pos].isSpace()))) {
2102 switch (in[pos].unicode()) {
2103 case '(':
2104 case '[':
2105 case '{':
2106 delimDepth++;
2107 arg += in[pos];
2108 pos++;
2109 break;
2110 case ')':
2111 case ']':
2112 case '}':
2113 delimDepth--;
2114 if (pos == startPos || delimDepth >= 0) {
2115 arg += in[pos];
2116 pos++;
2118 break;
2119 case '\\':
2120 if (verbatim) {
2121 arg += in[pos];
2122 pos++;
2124 else {
2125 pos++;
2126 if (pos < (int) in.length()) {
2127 if (in[pos].isLetterOrNumber())
2128 break;
2129 arg += in[pos];
2130 if (in[pos].isSpace()) {
2131 skipAllSpaces();
2133 else {
2134 pos++;
2138 break;
2139 default:
2140 arg += in[pos];
2141 pos++;
2144 if ((arg.length() > 1) &&
2145 (QString(".,:;!?").indexOf(in[pos - 1]) != -1) &&
2146 !arg.endsWith("...")) {
2147 arg.truncate(arg.length() - 1);
2148 pos--;
2150 if (arg.length() > 2 && in.mid(pos - 2, 2) == "'s") {
2151 arg.truncate(arg.length() - 2);
2152 pos -= 2;
2155 return arg.simplified();
2158 QString DocParser::getOptionalArgument()
2160 skipSpacesOrOneEndl();
2161 if (pos + 1 < (int) in.length() && in[pos] == '\\' &&
2162 in[pos + 1].isLetterOrNumber()) {
2163 return "";
2165 else {
2166 return getArgument();
2170 QString DocParser::getRestOfLine()
2172 QString t;
2174 skipSpacesOnLine();
2176 bool trailingSlash = false;
2178 do {
2179 int begin = pos;
2181 while (pos < in.size() && in[pos] != '\n') {
2182 if (in[pos] == '\\' && !trailingSlash) {
2183 trailingSlash = true;
2184 ++pos;
2185 while ((pos < in.size()) &&
2186 in[pos].isSpace() &&
2187 (in[pos] != '\n'))
2188 ++pos;
2190 else {
2191 trailingSlash = false;
2192 ++pos;
2196 if (!t.isEmpty())
2197 t += " ";
2198 t += in.mid(begin, pos - begin).simplified();
2200 if (trailingSlash) {
2201 t.chop(1);
2202 t = t.simplified();
2204 if (pos < in.size())
2205 ++pos;
2206 } while (pos < in.size() && trailingSlash);
2208 return t;
2212 The metacommand argument is normally the remaining text to
2213 the right of the metacommand itself. The extra blanks are
2214 stripped and the argument string is returned.
2216 QString DocParser::getMetaCommandArgument(const QString &cmdStr)
2218 skipSpacesOnLine();
2220 int begin = pos;
2221 int parenDepth = 0;
2223 while (pos < in.size() && (in[pos] != '\n' || parenDepth > 0)) {
2224 if (in.at(pos) == '(')
2225 ++parenDepth;
2226 else if (in.at(pos) == ')')
2227 --parenDepth;
2229 ++pos;
2231 if (pos == in.size() && parenDepth > 0) {
2232 pos = begin;
2233 location().warning(tr("Unbalanced parentheses in '%1'").arg(cmdStr));
2236 QString t = in.mid(begin, pos - begin).simplified();
2237 skipSpacesOnLine();
2238 return t;
2241 QString DocParser::getUntilEnd(int cmd)
2243 int endCmd = endCmdFor(cmd);
2244 QRegExp rx("\\\\" + cmdName(endCmd) + "\\b");
2245 QString t;
2246 int end = rx.indexIn(in, pos);
2248 if (end == -1) {
2249 location().warning(tr("Missing '\\%1'").arg(cmdName(endCmd)));
2250 pos = in.length();
2252 else {
2253 t = in.mid(pos, end - pos);
2254 pos = end + rx.matchedLength();
2256 return t;
2259 QString DocParser::getCode(int cmd, CodeMarker *marker)
2261 QString code = untabifyEtc(getUntilEnd(cmd));
2262 int indent = indentLevel(code);
2263 if (indent < minIndent)
2264 minIndent = indent;
2265 code = unindent(minIndent, code);
2266 marker = CodeMarker::markerForCode(code);
2267 return marker->markedUpCode(code, 0, "");
2271 Was used only for generating doxygen output.
2273 QString DocParser::getUnmarkedCode(int cmd)
2275 QString code = getUntilEnd(cmd);
2276 #if 0
2277 int indent = indentLevel(code);
2278 if (indent < minIndent)
2279 minIndent = indent;
2280 code = unindent(minIndent, code);
2281 #endif
2282 return code;
2285 bool DocParser::isBlankLine()
2287 int i = pos;
2289 while (i < len && in[i].isSpace()) {
2290 if (in[i] == '\n')
2291 return true;
2292 i++;
2294 return false;
2297 bool DocParser::isLeftBraceAhead()
2299 int numEndl = 0;
2300 int i = pos;
2302 while (i < len && in[i].isSpace() && numEndl < 2) {
2303 // ### bug with '\\'
2304 if (in[i] == '\n')
2305 numEndl++;
2306 i++;
2308 return numEndl < 2 && i < len && in[i] == '{';
2311 void DocParser::skipSpacesOnLine()
2313 while ((pos < in.length()) &&
2314 in[pos].isSpace() &&
2315 (in[pos].unicode() != '\n'))
2316 ++pos;
2319 void DocParser::skipSpacesOrOneEndl()
2321 int firstEndl = -1;
2322 while (pos < (int) in.length() && in[pos].isSpace()) {
2323 QChar ch = in[pos];
2324 if (ch == '\n') {
2325 if (firstEndl == -1) {
2326 firstEndl = pos;
2328 else {
2329 pos = firstEndl;
2330 break;
2333 pos++;
2337 void DocParser::skipAllSpaces()
2339 while (pos < len && in[pos].isSpace())
2340 pos++;
2343 void DocParser::skipToNextPreprocessorCommand()
2345 QRegExp rx("\\\\(?:" + cmdName(CMD_IF) + "|" +
2346 cmdName(CMD_ELSE) + "|" +
2347 cmdName(CMD_ENDIF) + ")\\b");
2348 int end = rx.indexIn(in, pos + 1); // ### + 1 necessary?
2350 if (end == -1)
2351 pos = in.length();
2352 else
2353 pos = end;
2356 int DocParser::endCmdFor(int cmd)
2358 switch (cmd) {
2359 case CMD_ABSTRACT:
2360 return CMD_ENDABSTRACT;
2361 case CMD_BADCODE:
2362 return CMD_ENDCODE;
2363 case CMD_CHAPTER:
2364 return CMD_ENDCHAPTER;
2365 case CMD_CODE:
2366 return CMD_ENDCODE;
2367 #ifdef QDOC_QML
2368 case CMD_QML:
2369 return CMD_ENDQML;
2370 case CMD_QMLTEXT:
2371 return CMD_ENDQMLTEXT;
2372 #endif
2373 case CMD_FOOTNOTE:
2374 return CMD_ENDFOOTNOTE;
2375 case CMD_LEGALESE:
2376 return CMD_ENDLEGALESE;
2377 case CMD_LINK:
2378 return CMD_ENDLINK;
2379 case CMD_LIST:
2380 return CMD_ENDLIST;
2381 case CMD_NEWCODE:
2382 return CMD_ENDCODE;
2383 case CMD_OLDCODE:
2384 return CMD_NEWCODE;
2385 case CMD_OMIT:
2386 return CMD_ENDOMIT;
2387 case CMD_PART:
2388 return CMD_ENDPART;
2389 case CMD_QUOTATION:
2390 return CMD_ENDQUOTATION;
2391 case CMD_RAW:
2392 return CMD_ENDRAW;
2393 case CMD_SECTION1:
2394 return CMD_ENDSECTION1;
2395 case CMD_SECTION2:
2396 return CMD_ENDSECTION2;
2397 case CMD_SECTION3:
2398 return CMD_ENDSECTION3;
2399 case CMD_SECTION4:
2400 return CMD_ENDSECTION4;
2401 case CMD_SIDEBAR:
2402 return CMD_ENDSIDEBAR;
2403 case CMD_TABLE:
2404 return CMD_ENDTABLE;
2405 default:
2406 return cmd;
2410 QString DocParser::cmdName(int cmd)
2412 return *cmds[cmd].alias;
2415 QString DocParser::endCmdName(int cmd)
2417 return cmdName(endCmdFor(cmd));
2420 QString DocParser::untabifyEtc(const QString& str)
2422 QString result;
2423 result.reserve(str.length());
2424 int column = 0;
2426 for (int i = 0; i < str.length(); i++) {
2427 const QChar c = str.at(i);
2428 if (c == QLatin1Char('\r'))
2429 continue;
2430 if (c == QLatin1Char('\t')) {
2431 result += " " + (column % tabSize);
2432 column = ((column / tabSize) + 1) * tabSize;
2433 continue;
2435 if (c == QLatin1Char('\n')) {
2436 while (result.endsWith(QLatin1Char(' ')))
2437 result.chop(1);
2438 result += c;
2439 column = 0;
2440 continue;
2442 result += c;
2443 column++;
2446 while (result.endsWith("\n\n"))
2447 result.truncate(result.length() - 1);
2448 while (result.startsWith("\n"))
2449 result = result.mid(1);
2451 return result;
2454 int DocParser::indentLevel(const QString& str)
2456 int minIndent = INT_MAX;
2457 int column = 0;
2459 for (int i = 0; i < (int) str.length(); i++) {
2460 if (str[i] == '\n') {
2461 column = 0;
2463 else {
2464 if (str[i] != ' ' && column < minIndent)
2465 minIndent = column;
2466 column++;
2469 return minIndent;
2472 QString DocParser::unindent(int level, const QString& str)
2474 if (level == 0)
2475 return str;
2477 QString t;
2478 int column = 0;
2480 for (int i = 0; i < (int) str.length(); i++) {
2481 if (str[i] == QLatin1Char('\n')) {
2482 t += '\n';
2483 column = 0;
2485 else {
2486 if (column >= level)
2487 t += str[i];
2488 column++;
2491 return t;
2494 QString DocParser::slashed(const QString& str)
2496 QString result = str;
2497 result.replace("/", "\\/");
2498 return "/" + result + "/";
2501 #define COMMAND_BRIEF Doc::alias("brief")
2503 #ifdef QDOC_QML
2504 #define COMMAND_QMLBRIEF Doc::alias("qmlbrief")
2505 #endif
2508 Doc::Doc(const Location& start_loc,
2509 const Location& end_loc,
2510 const QString& source,
2511 const QSet<QString>& metaCommandSet)
2513 priv = new DocPrivate(start_loc,end_loc,source);
2514 DocParser parser;
2515 parser.parse(source,priv,metaCommandSet);
2518 Doc::Doc(const Doc& doc)
2519 : priv(0)
2521 operator=(doc);
2524 Doc::~Doc()
2526 if (priv && priv->deref())
2527 delete priv;
2530 Doc &Doc::operator=(const Doc& doc)
2532 if (doc.priv)
2533 doc.priv->ref();
2534 if (priv && priv->deref())
2535 delete priv;
2536 priv = doc.priv;
2537 return *this;
2540 void Doc::renameParameters(const QStringList &oldNames,
2541 const QStringList &newNames)
2543 if (priv && oldNames != newNames) {
2544 detach();
2546 priv->params = newNames.toSet();
2548 Atom *atom = priv->text.firstAtom();
2549 while (atom) {
2550 if (atom->type() == Atom::FormattingLeft
2551 && atom->string() == ATOM_FORMATTING_PARAMETER) {
2552 atom = atom->next();
2553 if (!atom)
2554 return;
2555 int index = oldNames.indexOf(atom->string());
2556 if (index != -1 && index < newNames.count())
2557 atom->setString(newNames.at(index));
2559 atom = atom->next();
2564 void Doc::simplifyEnumDoc()
2566 if (priv) {
2567 if (priv->isEnumDocSimplifiable()) {
2568 detach();
2570 Text newText;
2572 Atom *atom = priv->text.firstAtom();
2573 while (atom) {
2574 if ((atom->type() == Atom::ListLeft) &&
2575 (atom->string() == ATOM_LIST_VALUE)) {
2576 while (atom && ((atom->type() != Atom::ListRight) ||
2577 (atom->string() != ATOM_LIST_VALUE)))
2578 atom = atom->next();
2579 if (atom)
2580 atom = atom->next();
2582 else {
2583 newText << *atom;
2584 atom = atom->next();
2587 priv->text = newText;
2592 void Doc::setBody(const Text &text)
2594 detach();
2595 priv->text = text;
2599 Returns the starting location of a qdoc comment.
2601 const Location &Doc::location() const
2603 static const Location dummy;
2604 return priv == 0 ? dummy : priv->start_loc;
2607 const QString &Doc::source() const
2609 static QString null;
2610 return priv == 0 ? null : priv->src;
2613 bool Doc::isEmpty() const
2615 return priv == 0 || priv->src.isEmpty();
2618 const Text& Doc::body() const
2620 static const Text dummy;
2621 return priv == 0 ? dummy : priv->text;
2624 Text Doc::briefText() const
2626 return body().subText(Atom::BriefLeft, Atom::BriefRight);
2629 Text Doc::trimmedBriefText(const QString &className) const
2631 QString classNameOnly = className;
2632 if (className.contains("::"))
2633 classNameOnly = className.split("::").last();
2635 Text originalText = briefText();
2636 Text resultText;
2637 const Atom *atom = originalText.firstAtom();
2638 if (atom) {
2639 QString briefStr;
2640 QString whats;
2641 bool standardWording = true;
2644 This code is really ugly. The entire \brief business
2645 should be rethought.
2647 while (atom) {
2648 if (atom->type() == Atom::AutoLink || atom->type() == Atom::String) {
2649 briefStr += atom->string();
2651 atom = atom->next();
2654 QStringList w = briefStr.split(" ");
2655 if (!w.isEmpty() && w.first() == "Returns") {
2657 else {
2658 if (!w.isEmpty() && w.first() == "The")
2659 w.removeFirst();
2660 else {
2661 location().warning(
2662 tr("Nonstandard wording in '\\%1' text for '%2' (expected 'The')")
2663 .arg(COMMAND_BRIEF).arg(className));
2664 standardWording = false;
2667 if (!w.isEmpty() && (w.first() == className || w.first() == classNameOnly))
2668 w.removeFirst();
2669 else {
2670 location().warning(
2671 tr("Nonstandard wording in '\\%1' text for '%2' (expected '%3')")
2672 .arg(COMMAND_BRIEF).arg(className).arg(className));
2673 standardWording = false;
2676 if (!w.isEmpty() && ((w.first() == "class") ||
2677 (w.first() == "function") ||
2678 (w.first() == "macro") ||
2679 (w.first() == "widget") ||
2680 (w.first() == "namespace") ||
2681 (w.first() == "header")))
2682 w.removeFirst();
2683 else {
2684 location().warning(
2685 tr("Nonstandard wording in '\\%1' text for '%2' ("
2686 "expected 'class', 'function', 'macro', 'widget', "
2687 "'namespace' or 'header')")
2688 .arg(COMMAND_BRIEF).arg(className));
2689 standardWording = false;
2692 if (!w.isEmpty() && (w.first() == "is" || w.first() == "provides"))
2693 w.removeFirst();
2695 if (!w.isEmpty() && (w.first() == "a" || w.first() == "an"))
2696 w.removeFirst();
2699 whats = w.join(" ");
2701 if (whats.endsWith("."))
2702 whats.truncate(whats.length() - 1);
2704 if (whats.isEmpty()) {
2705 location().warning(
2706 tr("Nonstandard wording in '\\%1' text for '%2' (expected more text)")
2707 .arg(COMMAND_BRIEF).arg(className));
2708 standardWording = false;
2710 else
2711 whats[0] = whats[0].toUpper();
2713 // ### move this once \brief is abolished for properties
2714 if (standardWording)
2715 resultText << whats;
2717 return resultText;
2720 Text Doc::legaleseText() const
2722 if (priv == 0 || !priv->hasLegalese)
2723 return Text();
2724 else
2725 return body().subText(Atom::LegaleseLeft, Atom::LegaleseRight);
2728 const QString& Doc::baseName() const
2730 static QString null;
2731 if (priv == 0 || priv->extra == 0) {
2732 return null;
2734 else {
2735 return priv->extra->baseName;
2739 Doc::SectioningUnit Doc::granularity() const
2741 if (priv == 0 || priv->extra == 0) {
2742 return DocPrivateExtra().granularity;
2744 else {
2745 return priv->extra->granularity;
2749 #if notyet // ###
2750 Doc::SectioningUnit Doc::sectioningUnit() const
2752 if (priv == 0 || priv->extra == 0) {
2753 return DocPrivateExtra().sectioningUnit;
2755 else {
2756 return priv->extra->sectioningUnit;
2759 #endif
2761 const QSet<QString> &Doc::parameterNames() const
2763 return priv == 0 ? *null_Set_QString() : priv->params;
2766 const QStringList &Doc::enumItemNames() const
2768 return priv == 0 ? *null_QStringList() : priv->enumItemList;
2771 const QStringList &Doc::omitEnumItemNames() const
2773 return priv == 0 ? *null_QStringList() : priv->omitEnumItemList;
2776 const QSet<QString> &Doc::metaCommandsUsed() const
2778 return priv == 0 ? *null_Set_QString() : priv->metacommandsUsed;
2781 QStringList Doc::metaCommandArgs(const QString& metacommand) const
2783 return priv == 0 ? QStringList() : priv->metaCommandMap.value(metacommand);
2786 const QList<Text> &Doc::alsoList() const
2788 return priv == 0 ? *null_QList_Text() : priv->alsoList;
2791 bool Doc::hasTableOfContents() const
2793 return priv && priv->extra && !priv->extra->tableOfContents.isEmpty();
2796 bool Doc::hasKeywords() const
2798 return priv && priv->extra && !priv->extra->keywords.isEmpty();
2801 bool Doc::hasTargets() const
2803 return priv && priv->extra && !priv->extra->targets.isEmpty();
2806 const QList<Atom *> &Doc::tableOfContents() const
2808 priv->constructExtra();
2809 return priv->extra->tableOfContents;
2812 const QList<int> &Doc::tableOfContentsLevels() const
2814 priv->constructExtra();
2815 return priv->extra->tableOfContentsLevels;
2818 const QList<Atom *> &Doc::keywords() const
2820 priv->constructExtra();
2821 return priv->extra->keywords;
2824 const QList<Atom *> &Doc::targets() const
2826 priv->constructExtra();
2827 return priv->extra->targets;
2830 const QStringMap &Doc::metaTagMap() const
2832 return priv && priv->extra ? priv->extra->metaMap : *null_QStringMap();
2835 void Doc::initialize(const Config& config)
2837 DocParser::tabSize = config.getInt(CONFIG_TABSIZE);
2838 DocParser::exampleFiles = config.getStringList(CONFIG_EXAMPLES);
2839 DocParser::exampleDirs = config.getStringList(CONFIG_EXAMPLEDIRS);
2840 DocParser::sourceFiles = config.getStringList(CONFIG_SOURCES);
2841 DocParser::sourceDirs = config.getStringList(CONFIG_SOURCEDIRS);
2842 DocParser::quoting = config.getBool(CONFIG_QUOTINGINFORMATION);
2844 #ifdef QDOC_QML
2845 QmlClassNode::qmlOnly = config.getBool(CONFIG_QMLONLY);
2846 #endif
2848 QStringMap reverseAliasMap;
2850 QSet<QString> commands = config.subVars(CONFIG_ALIAS);
2851 QSet<QString>::ConstIterator c = commands.begin();
2852 while (c != commands.end()) {
2853 QString alias = config.getString(CONFIG_ALIAS + Config::dot + *c);
2854 if (reverseAliasMap.contains(alias)) {
2855 config.lastLocation().warning(tr("Command name '\\%1' cannot stand"
2856 " for both '\\%2' and '\\%3'")
2857 .arg(alias)
2858 .arg(reverseAliasMap[alias])
2859 .arg(*c));
2861 else {
2862 reverseAliasMap.insert(alias, *c);
2864 aliasMap()->insert(*c, alias);
2865 ++c;
2868 int i = 0;
2869 while (cmds[i].english) {
2870 cmds[i].alias = new QString(alias(cmds[i].english));
2871 cmdHash()->insert(*cmds[i].alias, cmds[i].no);
2873 if (cmds[i].no != i)
2874 Location::internalError(tr("command %1 missing").arg(i));
2875 i++;
2878 QSet<QString> macroNames = config.subVars(CONFIG_MACRO);
2879 QSet<QString>::ConstIterator n = macroNames.begin();
2880 while (n != macroNames.end()) {
2881 QString macroDotName = CONFIG_MACRO + Config::dot + *n;
2882 Macro macro;
2883 macro.numParams = -1;
2884 macro.defaultDef = config.getString(macroDotName);
2885 if (!macro.defaultDef.isEmpty()) {
2886 macro.defaultDefLocation = config.lastLocation();
2887 macro.numParams = Config::numParams(macro.defaultDef);
2889 bool silent = false;
2891 QSet<QString> formats = config.subVars(macroDotName);
2892 QSet<QString>::ConstIterator f = formats.begin();
2893 while (f != formats.end()) {
2894 QString def = config.getString(macroDotName + Config::dot + *f);
2895 if (!def.isEmpty()) {
2896 macro.otherDefs.insert(*f, def);
2897 int m = Config::numParams(macro.defaultDef);
2898 if (macro.numParams == -1) {
2899 macro.numParams = m;
2901 else if (macro.numParams != m) {
2902 if (!silent) {
2903 QString other = tr("default");
2904 if (macro.defaultDef.isEmpty())
2905 other = macro.otherDefs.begin().key();
2906 config.lastLocation().warning(tr("Macro '\\%1' takes"
2907 " inconsistent number"
2908 " of arguments (%2"
2909 " %3, %4 %5)")
2910 .arg(*n)
2911 .arg(*f)
2912 .arg(m)
2913 .arg(other)
2914 .arg(macro.numParams));
2915 silent = true;
2917 if (macro.numParams < m)
2918 macro.numParams = m;
2921 ++f;
2924 if (macro.numParams != -1)
2925 macroHash()->insert(*n, macro);
2926 ++n;
2930 void Doc::terminate()
2932 DocParser::exampleFiles.clear();
2933 DocParser::exampleDirs.clear();
2934 DocParser::sourceFiles.clear();
2935 DocParser::sourceDirs.clear();
2936 aliasMap()->clear();
2937 cmdHash()->clear();
2938 macroHash()->clear();
2940 int i = 0;
2941 while (cmds[i].english) {
2942 delete cmds[i].alias;
2943 cmds[i].alias = 0;
2944 ++i;
2948 QString Doc::alias(const QString &english)
2950 return aliasMap()->value(english, english);
2954 Trims the deadwood out of \a str. i.e., this function
2955 cleans up \a str.
2957 void Doc::trimCStyleComment(Location& location, QString& str)
2959 QString cleaned;
2960 Location m = location;
2961 bool metAsterColumn = true;
2962 int asterColumn = location.columnNo() + 1;
2963 int i;
2965 for (i = 0; i < (int) str.length(); i++) {
2966 if (m.columnNo() == asterColumn) {
2967 if (str[i] != '*')
2968 break;
2969 cleaned += ' ';
2970 metAsterColumn = true;
2972 else {
2973 if (str[i] == '\n') {
2974 if (!metAsterColumn)
2975 break;
2976 metAsterColumn = false;
2978 cleaned += str[i];
2980 m.advance(str[i]);
2982 if (cleaned.length() == str.length())
2983 str = cleaned;
2985 for (int i = 0; i < 3; i++)
2986 location.advance(str[i]);
2987 str = str.mid(3, str.length() - 5);
2990 CodeMarker *Doc::quoteFromFile(const Location &location,
2991 Quoter &quoter,
2992 const QString &fileName)
2994 quoter.reset();
2996 QString code;
2998 QString userFriendlyFilePath;
2999 QString filePath = Config::findFile(location,
3000 DocParser::exampleFiles,
3001 DocParser::exampleDirs,
3002 fileName, userFriendlyFilePath);
3003 if (filePath.isEmpty()) {
3004 location.warning(tr("Cannot find example file '%1'").arg(fileName));
3006 else {
3007 QFile inFile(filePath);
3008 if (!inFile.open(QFile::ReadOnly)) {
3009 location.warning(tr("Cannot open example file '%1'").arg(userFriendlyFilePath));
3011 else {
3012 QTextStream inStream(&inFile);
3013 code = DocParser::untabifyEtc(inStream.readAll());
3017 QString dirPath = QFileInfo(filePath).path();
3018 CodeMarker *marker = CodeMarker::markerForFileName(fileName);
3019 quoter.quoteFromFile(userFriendlyFilePath,
3020 code,
3021 marker->markedUpCode(code, 0, dirPath));
3022 return marker;
3025 QString Doc::canonicalTitle(const QString &title)
3027 // The code below is equivalent to the following chunk, but _much_
3028 // faster (accounts for ~10% of total running time)
3030 // QRegExp attributeExpr("[^A-Za-z0-9]+");
3031 // QString result = title.toLower();
3032 // result.replace(attributeExpr, " ");
3033 // result = result.simplified();
3034 // result.replace(QLatin1Char(' '), QLatin1Char('-'));
3036 QString result;
3037 result.reserve(title.size());
3039 bool slurping = false;
3040 bool begun = false;
3041 int lastAlnum = 0;
3042 for (int i = 0; i != title.size(); ++i) {
3043 uint c = title.at(i).unicode();
3044 if (c >= 'A' && c <= 'Z')
3045 c -= 'A' - 'a';
3046 bool alnum = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
3047 if (alnum) {
3048 result += QLatin1Char(c);
3049 begun = true;
3050 slurping = false;
3051 lastAlnum = result.size();
3053 else if (!slurping) {
3054 if (begun)
3055 result += QLatin1Char('-');
3056 slurping = true;
3058 else {
3059 // !alnum && slurping -> nothin
3062 result.truncate(lastAlnum);
3063 return result;
3066 void Doc::detach()
3068 if (!priv) {
3069 priv = new DocPrivate;
3070 return;
3072 if (priv->count == 1)
3073 return;
3075 --priv->count;
3077 DocPrivate *newPriv = new DocPrivate(*priv);
3078 newPriv->count = 1;
3079 if (priv->extra)
3080 newPriv->extra = new DocPrivateExtra(*priv->extra);
3082 priv = newPriv;
3085 QT_END_NAMESPACE