source: trunk/tools/qdoc3/doc.cpp@ 374

Last change on this file since 374 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 157.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the tools applications of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
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>
59
60QT_BEGIN_NAMESPACE
61
62Q_GLOBAL_STATIC(QSet<QString>, null_Set_QString)
63Q_GLOBAL_STATIC(QStringList, null_QStringList)
64Q_GLOBAL_STATIC(QList<Text>, null_QList_Text)
65Q_GLOBAL_STATIC(QStringMap, null_QStringMap)
66
67struct Macro
68{
69 QString defaultDef;
70 Location defaultDefLocation;
71 QStringMap otherDefs;
72 int numParams;
73};
74
75enum {
76 CMD_A, CMD_ABSTRACT, CMD_BADCODE, CMD_BASENAME, CMD_BOLD,
77 CMD_BRIEF, CMD_C, CMD_CAPTION, CMD_CHAPTER, CMD_CODE,
78 CMD_CODELINE, CMD_DOTS, CMD_ELSE, CMD_ENDABSTRACT,
79 CMD_ENDCHAPTER, CMD_ENDCODE, CMD_ENDFOOTNOTE, CMD_ENDIF,
80 CMD_ENDLEGALESE, CMD_ENDLINK, CMD_ENDLIST, CMD_ENDOMIT,
81 CMD_ENDPART, CMD_ENDQUOTATION, CMD_ENDRAW, CMD_ENDSECTION1,
82 CMD_ENDSECTION2, CMD_ENDSECTION3, CMD_ENDSECTION4,
83 CMD_ENDSIDEBAR, CMD_ENDTABLE, CMD_EXPIRE, CMD_FOOTNOTE,
84 CMD_GENERATELIST, CMD_GRANULARITY, CMD_HEADER, CMD_I,
85 CMD_IF, CMD_IMAGE, CMD_INCLUDE, CMD_INLINEIMAGE, CMD_INDEX,
86 CMD_KEYWORD, CMD_L, CMD_LEGALESE, CMD_LINK, CMD_LIST,
87 CMD_META, CMD_NEWCODE, CMD_O, CMD_OLDCODE, CMD_OMIT,
88 CMD_OMITVALUE, CMD_OVERLOAD,
89 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_SKIPLINE, CMD_SKIPTO,
94 CMD_SKIPUNTIL, CMD_SNIPPET, CMD_SUB, CMD_SUP, CMD_TABLE,
95 CMD_TABLEOFCONTENTS, CMD_TARGET, CMD_TT, CMD_UNDERLINE,
96 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
102};
103
104static struct {
105 const char *english;
106 int no;
107 QString *alias;
108} cmds[] = {
109 { "a", CMD_A, 0 },
110 { "abstract", CMD_ABSTRACT, 0 },
111 { "badcode", CMD_BADCODE, 0 },
112 { "basename", CMD_BASENAME, 0 }, // ### don't document for now
113 { "bold", CMD_BOLD, 0 },
114 { "brief", CMD_BRIEF, 0 },
115 { "c", CMD_C, 0 },
116 { "caption", CMD_CAPTION, 0 },
117 { "chapter", CMD_CHAPTER, 0 },
118 { "code", CMD_CODE, 0 },
119 { "codeline", CMD_CODELINE, 0},
120 { "dots", CMD_DOTS, 0 },
121 { "else", CMD_ELSE, 0 },
122 { "endabstract", CMD_ENDABSTRACT, 0 },
123 { "endchapter", CMD_ENDCHAPTER, 0 },
124 { "endcode", CMD_ENDCODE, 0 },
125 { "endfootnote", CMD_ENDFOOTNOTE, 0 },
126 { "endif", CMD_ENDIF, 0 },
127 { "endlegalese", CMD_ENDLEGALESE, 0 },
128 { "endlink", CMD_ENDLINK, 0 },
129 { "endlist", CMD_ENDLIST, 0 },
130 { "endomit", CMD_ENDOMIT, 0 },
131 { "endpart", CMD_ENDPART, 0 },
132 { "endquotation", CMD_ENDQUOTATION, 0 },
133 { "endraw", CMD_ENDRAW, 0 },
134 { "endsection1", CMD_ENDSECTION1, 0 }, // ### don't document for now
135 { "endsection2", CMD_ENDSECTION2, 0 }, // ### don't document for now
136 { "endsection3", CMD_ENDSECTION3, 0 }, // ### don't document for now
137 { "endsection4", CMD_ENDSECTION4, 0 }, // ### don't document for now
138 { "endsidebar", CMD_ENDSIDEBAR, 0 },
139 { "endtable", CMD_ENDTABLE, 0 },
140 { "expire", CMD_EXPIRE, 0 },
141 { "footnote", CMD_FOOTNOTE, 0 },
142 { "generatelist", CMD_GENERATELIST, 0 },
143 { "granularity", CMD_GRANULARITY, 0 }, // ### don't document for now
144 { "header", CMD_HEADER, 0 },
145 { "i", CMD_I, 0 },
146 { "if", CMD_IF, 0 },
147 { "image", CMD_IMAGE, 0 },
148 { "include", CMD_INCLUDE, 0 },
149 { "inlineimage", CMD_INLINEIMAGE, 0 },
150 { "index", CMD_INDEX, 0 }, // ### don't document for now
151 { "keyword", CMD_KEYWORD, 0 },
152 { "l", CMD_L, 0 },
153 { "legalese", CMD_LEGALESE, 0 },
154 { "link", CMD_LINK, 0 },
155 { "list", CMD_LIST, 0 },
156 { "meta", CMD_META, 0 },
157 { "newcode", CMD_NEWCODE, 0 },
158 { "o", CMD_O, 0 },
159 { "oldcode", CMD_OLDCODE, 0 },
160 { "omit", CMD_OMIT, 0 },
161 { "omitvalue", CMD_OMITVALUE, 0 },
162 { "overload", CMD_OVERLOAD, 0 },
163 { "part", CMD_PART, 0 },
164 { "printline", CMD_PRINTLINE, 0 },
165 { "printto", CMD_PRINTTO, 0 },
166 { "printuntil", CMD_PRINTUNTIL, 0 },
167 { "quotation", CMD_QUOTATION, 0 },
168 { "quotefile", CMD_QUOTEFILE, 0 },
169 { "quotefromfile", CMD_QUOTEFROMFILE, 0 },
170 { "quotefunction", CMD_QUOTEFUNCTION, 0 }, // ### don't document for now
171 { "raw", CMD_RAW, 0 },
172 { "row", CMD_ROW, 0 },
173 { "sa", CMD_SA, 0 },
174 { "section1", CMD_SECTION1, 0 },
175 { "section2", CMD_SECTION2, 0 },
176 { "section3", CMD_SECTION3, 0 },
177 { "section4", CMD_SECTION4, 0 },
178 { "sidebar", CMD_SIDEBAR, 0 }, // ### don't document for now
179 { "skipline", CMD_SKIPLINE, 0 },
180 { "skipto", CMD_SKIPTO, 0 },
181 { "skipuntil", CMD_SKIPUNTIL, 0 },
182 { "snippet", CMD_SNIPPET, 0 },
183 { "sub", CMD_SUB, 0 },
184 { "sup", CMD_SUP, 0 },
185 { "table", CMD_TABLE, 0 },
186 { "tableofcontents", CMD_TABLEOFCONTENTS, 0 },
187 { "target", CMD_TARGET, 0 },
188 { "tt", CMD_TT, 0 },
189 { "underline", CMD_UNDERLINE, 0 },
190 { "unicode", CMD_UNICODE, 0 },
191 { "value", CMD_VALUE, 0 },
192 { "warning", CMD_WARNING, 0 },
193#ifdef QDOC_QML
194 { "qml", CMD_QML, 0 },
195 { "endqml", CMD_ENDQML, 0 },
196 { "cpp", CMD_CPP, 0 },
197 { "endcpp", CMD_ENDCPP, 0 },
198 { "qmltext", CMD_QMLTEXT, 0 },
199 { "endqmltext", CMD_ENDQMLTEXT, 0 },
200 { "cpptext", CMD_CPPTEXT, 0 },
201 { "endcpptext", CMD_ENDCPPTEXT, 0 },
202#endif
203 { 0, 0, 0 }
204};
205
206typedef QHash<QString, int> QHash_QString_int;
207typedef QHash<QString, Macro> QHash_QString_Macro;
208
209Q_GLOBAL_STATIC(QStringMap, aliasMap)
210Q_GLOBAL_STATIC(QHash_QString_int, cmdHash)
211Q_GLOBAL_STATIC(QHash_QString_Macro, macroHash)
212
213class DocPrivateExtra
214{
215 public:
216 QString baseName;
217 Doc::SectioningUnit granularity;
218 Doc::SectioningUnit sectioningUnit; // ###
219 QList<Atom*> tableOfContents;
220 QList<int> tableOfContentsLevels;
221 QList<Atom*> keywords;
222 QList<Atom*> targets;
223 QStringMap metaMap;
224
225 DocPrivateExtra()
226 : granularity(Doc::Part) { }
227};
228
229struct Shared // ### get rid of
230{
231 Shared()
232 : count(1) { }
233 void ref() { ++count; }
234 bool deref() { return (--count == 0); }
235
236 int count;
237};
238
239static QString cleanLink(const QString &link)
240{
241 int colonPos = link.indexOf(':');
242 if ((colonPos == -1) ||
243 (!link.startsWith("file:") && !link.startsWith("mailto:")))
244 return link;
245 return link.mid(colonPos + 1).simplified();
246}
247
248class DocPrivate : public Shared
249{
250 public:
251 DocPrivate(const Location& start = Location::null,
252 const Location& end = Location::null,
253 const QString& source = "");
254 ~DocPrivate();
255
256 void addAlso(const Text& also);
257 void constructExtra();
258 bool isEnumDocSimplifiable() const;
259
260 // ### move some of this in DocPrivateExtra
261 Location start_loc;
262 Location end_loc;
263 QString src;
264 Text text;
265 QSet<QString> params;
266 QList<Text> alsoList;
267 QStringList enumItemList;
268 QStringList omitEnumItemList;
269 QSet<QString> metacommandsUsed;
270 QCommandMap metaCommandMap;
271 bool hasLegalese : 1;
272 bool hasSectioningUnits : 1;
273 DocPrivateExtra *extra;
274};
275
276DocPrivate::DocPrivate(const Location& start,
277 const Location& end,
278 const QString& source)
279 : start_loc(start),
280 end_loc(end),
281 src(source),
282 hasLegalese(false),
283 hasSectioningUnits(false),
284 extra(0)
285{
286 // nothing.
287}
288
289DocPrivate::~DocPrivate()
290{
291 delete extra;
292}
293
294void DocPrivate::addAlso(const Text& also)
295{
296 alsoList.append(also);
297}
298
299void DocPrivate::constructExtra()
300{
301 if (extra == 0)
302 extra = new DocPrivateExtra;
303}
304
305bool DocPrivate::isEnumDocSimplifiable() const
306{
307 bool justMetColon = false;
308 int numValueTables = 0;
309
310 const Atom *atom = text.firstAtom();
311 while (atom) {
312 if (atom->type() == Atom::AutoLink || atom->type() == Atom::String) {
313 justMetColon = atom->string().endsWith(":");
314 }
315 else if ((atom->type() == Atom::ListLeft) &&
316 (atom->string() == ATOM_LIST_VALUE)) {
317 if (justMetColon || numValueTables > 0)
318 return false;
319 ++numValueTables;
320 }
321 atom = atom->next();
322 }
323 return true;
324}
325
326class DocParser
327{
328 public:
329 void parse(const QString &source,
330 DocPrivate *docPrivate,
331 const QSet<QString> &metaCommandSet);
332
333 static int endCmdFor(int cmd);
334 static QString cmdName(int cmd);
335 static QString endCmdName(int cmd);
336 static QString untabifyEtc(const QString& str);
337 static int indentLevel(const QString& str);
338 static QString unindent(int level, const QString& str);
339 static QString slashed(const QString& str);
340
341 static int tabSize;
342 static QStringList exampleFiles;
343 static QStringList exampleDirs;
344 static QStringList sourceFiles;
345 static QStringList sourceDirs;
346 static bool quoting;
347
348 private:
349 Location& location();
350 QString detailsUnknownCommand(const QSet<QString>& metaCommandSet,
351 const QString& str);
352 void checkExpiry(const QString& date);
353 void insertBaseName(const QString &baseName);
354 void insertTarget(const QString& target, bool keyword);
355 void include(const QString& fileName);
356 void startFormat(const QString& format, int cmd);
357 bool openCommand(int cmd);
358 bool closeCommand(int endCmd);
359 void startSection(Doc::SectioningUnit unit, int cmd);
360 void endSection(int unit, int endCmd);
361 void parseAlso();
362 void append(Atom::Type type, const QString& string = "");
363 void appendChar(QChar ch);
364 void appendWord(const QString &word);
365 void appendToCode(const QString &code);
366 void startNewPara();
367 void enterPara(Atom::Type leftType = Atom::ParaLeft,
368 Atom::Type rightType = Atom::ParaRight,
369 const QString& string = "");
370 void leavePara();
371 void leaveValue();
372 void leaveValueList();
373 void leaveTableRow();
374 CodeMarker *quoteFromFile();
375 void expandMacro(const QString& name, const QString& def, int numParams);
376 Doc::SectioningUnit getSectioningUnit();
377 QString getArgument(bool verbatim = false);
378 QString getOptionalArgument();
379 QString getRestOfLine();
380 QString getMetaCommandArgument(const QString &cmdStr);
381 QString getUntilEnd(int cmd);
382 QString getCode(int cmd, CodeMarker *marker);
383 QString getUnmarkedCode(int cmd);
384
385 bool isBlankLine();
386 bool isLeftBraceAhead();
387 void skipSpacesOnLine();
388 void skipSpacesOrOneEndl();
389 void skipAllSpaces();
390 void skipToNextPreprocessorCommand();
391
392 QStack<int> openedInputs;
393
394 QString in;
395 int pos;
396 int len;
397 Location cachedLoc;
398 int cachedPos;
399
400 DocPrivate *priv;
401 enum ParaState { OutsidePara, InsideSingleLinePara, InsideMultiLinePara };
402 ParaState paraState;
403 bool inTableHeader;
404 bool inTableRow;
405 bool inTableItem;
406 bool indexStartedPara; // ### rename
407 Atom::Type pendingParaLeftType;
408 Atom::Type pendingParaRightType;
409 QString pendingParaString;
410
411 int braceDepth;
412 int minIndent;
413 Doc::SectioningUnit currentSectioningUnit;
414 QMap<QString, Location> targetMap;
415 QMap<int, QString> pendingFormats;
416 QStack<int> openedCommands;
417 QStack<OpenedList> openedLists;
418 Quoter quoter;
419};
420
421int DocParser::tabSize;
422QStringList DocParser::exampleFiles;
423QStringList DocParser::exampleDirs;
424QStringList DocParser::sourceFiles;
425QStringList DocParser::sourceDirs;
426bool DocParser::quoting;
427
428/*!
429 Parse the \a source string to build a Text data structure
430 in \a docPrivate. The Text data structure is a linked list
431 of Atoms.
432
433 \a metaCommandSet is the set of metacommands that may be
434 found in \a source. These metacommands are not markup text
435 commands. They are topic commands and related metacommands.
436 */
437void DocParser::parse(const QString& source,
438 DocPrivate *docPrivate,
439 const QSet<QString>& metaCommandSet)
440{
441 in = source;
442 pos = 0;
443 len = in.length();
444 cachedLoc = docPrivate->start_loc;
445 cachedPos = 0;
446 priv = docPrivate;
447 priv->text << Atom::Nop;
448
449 paraState = OutsidePara;
450 inTableHeader = false;
451 inTableRow = false;
452 inTableItem = false;
453 indexStartedPara = false;
454 pendingParaLeftType = Atom::Nop;
455 pendingParaRightType = Atom::Nop;
456
457 braceDepth = 0;
458 minIndent = INT_MAX;
459 currentSectioningUnit = Doc::Book;
460 openedCommands.push(CMD_OMIT);
461 quoter.reset();
462
463 CodeMarker *marker = 0;
464 Atom *currentLinkAtom = 0;
465 QString x;
466 QStack<bool> preprocessorSkipping;
467 int numPreprocessorSkipping = 0;
468
469 while (pos < len) {
470 QChar ch = in.at(pos);
471
472 switch (ch.unicode()) {
473 case '\\':
474 {
475 QString cmdStr;
476 pos++;
477 while (pos < len) {
478 ch = in.at(pos);
479 if (ch.isLetterOrNumber()) {
480 cmdStr += ch;
481 pos++;
482 }
483 else {
484 break;
485 }
486 }
487 if (cmdStr.isEmpty()) {
488 if (pos < len) {
489 enterPara();
490 if (in.at(pos).isSpace()) {
491 skipAllSpaces();
492 appendChar(QLatin1Char(' '));
493 }
494 else {
495 appendChar(in.at(pos++));
496 }
497 }
498 }
499 else {
500 int cmd = cmdHash()->value(cmdStr,NOT_A_CMD);
501 switch (cmd) {
502 case CMD_A:
503 enterPara();
504 x = getArgument();
505 append(Atom::FormattingLeft,ATOM_FORMATTING_PARAMETER);
506 append(Atom::String, x);
507 append(Atom::FormattingRight,ATOM_FORMATTING_PARAMETER);
508 priv->params.insert(x);
509 break;
510 case CMD_ABSTRACT:
511 if (openCommand(cmd)) {
512 leavePara();
513 append(Atom::AbstractLeft);
514 }
515 break;
516 case CMD_BADCODE:
517 leavePara();
518#ifdef QDOC2DOX
519 if (DoxWriter::isDoxPass())
520 append(Atom::CodeBad,getUnmarkedCode(CMD_BADCODE));
521 else
522 append(Atom::CodeBad,getCode(CMD_BADCODE, marker));
523#else
524 append(Atom::CodeBad,getCode(CMD_BADCODE, marker));
525#endif
526 break;
527 case CMD_BASENAME:
528 leavePara();
529 insertBaseName(getArgument());
530 break;
531 case CMD_BOLD:
532 startFormat(ATOM_FORMATTING_BOLD, cmd);
533 break;
534 case CMD_BRIEF:
535 leavePara();
536 enterPara(Atom::BriefLeft, Atom::BriefRight);
537 break;
538 case CMD_C:
539 enterPara();
540 x = untabifyEtc(getArgument(true));
541#ifdef QDOC2DOX
542 if (DoxWriter::isDoxPass())
543 append(Atom::C, x);
544 else {
545 marker = CodeMarker::markerForCode(x);
546 append(Atom::C, marker->markedUpCode(x, 0, ""));
547 }
548#else
549 marker = CodeMarker::markerForCode(x);
550 append(Atom::C, marker->markedUpCode(x, 0, ""));
551#endif
552 break;
553 case CMD_CAPTION:
554 leavePara();
555 /* ... */
556 break;
557 case CMD_CHAPTER:
558 startSection(Doc::Chapter, cmd);
559 break;
560 case CMD_CODE:
561 leavePara();
562#ifdef QDOC2DOX
563 if (DoxWriter::isDoxPass())
564 append(Atom::Code, getUnmarkedCode(CMD_CODE));
565 else
566 append(Atom::Code, getCode(CMD_CODE, marker));
567#else
568 append(Atom::Code, getCode(CMD_CODE, marker));
569#endif
570 break;
571#ifdef QDOC_QML
572 case CMD_QML:
573 leavePara();
574 append(Atom::Qml, getCode(CMD_QML, marker));
575 break;
576 case CMD_QMLTEXT:
577 append(Atom::QmlText);
578 break;
579#endif
580 case CMD_CODELINE:
581 {
582#ifdef QDOC2DOX
583 if (!quoting && !DoxWriter::isDoxPass()) {
584 if (priv->text.lastAtom()->type() == Atom::Code
585 && priv->text.lastAtom()->string().endsWith("\n\n"))
586 priv->text.lastAtom()->chopString();
587 appendToCode("\n");
588 } lse {
589 append(Atom::CodeQuoteCommand, cmdStr);
590 append(Atom::CodeQuoteArgument, " ");
591 }
592#else
593 if (!quoting) {
594 if (priv->text.lastAtom()->type() == Atom::Code
595 && priv->text.lastAtom()->string().endsWith("\n\n"))
596 priv->text.lastAtom()->chopString();
597 appendToCode("\n");
598 }
599 else {
600 append(Atom::CodeQuoteCommand, cmdStr);
601 append(Atom::CodeQuoteArgument, " ");
602 }
603#endif
604 }
605 break;
606 case CMD_DOTS:
607 {
608#ifdef QDOC2DOX
609 if (DoxWriter::isDoxPass()) {
610 append(Atom::CodeQuoteCommand, cmdStr);
611 append(Atom::CodeQuoteArgument, " ...");
612 }
613 else if (!quoting) {
614 if (priv->text.lastAtom()->type() == Atom::Code
615 && priv->text.lastAtom()->string().endsWith("\n\n"))
616 priv->text.lastAtom()->chopString();
617
618 QString arg = getOptionalArgument();
619 int indent = 4;
620 if (!arg.isEmpty())
621 indent = arg.toInt();
622 for (int i = 0; i < indent; ++i)
623 appendToCode(" ");
624 appendToCode("...\n");
625 }
626 else {
627 append(Atom::CodeQuoteCommand, cmdStr);
628 QString arg = getOptionalArgument();
629 if (arg.isEmpty())
630 arg = "4";
631 append(Atom::CodeQuoteArgument, arg);
632 }
633#else
634 if (!quoting) {
635 if (priv->text.lastAtom()->type() == Atom::Code
636 && priv->text.lastAtom()->string().endsWith("\n\n"))
637 priv->text.lastAtom()->chopString();
638
639 QString arg = getOptionalArgument();
640 int indent = 4;
641 if (!arg.isEmpty())
642 indent = arg.toInt();
643 for (int i = 0; i < indent; ++i)
644 appendToCode(" ");
645 appendToCode("...\n");
646 }
647 else {
648 append(Atom::CodeQuoteCommand, cmdStr);
649 QString arg = getOptionalArgument();
650 if (arg.isEmpty())
651 arg = "4";
652 append(Atom::CodeQuoteArgument, arg);
653 }
654#endif
655 }
656 break;
657 case CMD_ELSE:
658 if (preprocessorSkipping.size() > 0) {
659 if (preprocessorSkipping.top()) {
660 --numPreprocessorSkipping;
661 }
662 else {
663 ++numPreprocessorSkipping;
664 }
665 preprocessorSkipping.top() = !preprocessorSkipping.top();
666 (void)getRestOfLine(); // ### should ensure that it's empty
667 if (numPreprocessorSkipping)
668 skipToNextPreprocessorCommand();
669 }
670 else {
671 location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ELSE)));
672 }
673 break;
674 case CMD_ENDABSTRACT:
675 if (closeCommand(cmd)) {
676 leavePara();
677 append(Atom::AbstractRight);
678 }
679 break;
680 case CMD_ENDCHAPTER:
681 endSection(0, cmd);
682 break;
683 case CMD_ENDCODE:
684 closeCommand(cmd);
685 break;
686#ifdef QDOC_QML
687 case CMD_ENDQML:
688 closeCommand(cmd);
689 break;
690 case CMD_ENDQMLTEXT:
691 append(Atom::EndQmlText);
692 break;
693#endif
694 case CMD_ENDFOOTNOTE:
695 if (closeCommand(cmd)) {
696 leavePara();
697 append(Atom::FootnoteRight);
698 paraState = InsideMultiLinePara; // ###
699 }
700 break;
701 case CMD_ENDIF:
702 if (preprocessorSkipping.count() > 0) {
703 if (preprocessorSkipping.pop())
704 --numPreprocessorSkipping;
705 (void)getRestOfLine(); // ### should ensure that it's empty
706 if (numPreprocessorSkipping)
707 skipToNextPreprocessorCommand();
708 }
709 else {
710 location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ENDIF)));
711 }
712 break;
713 case CMD_ENDLEGALESE:
714 if (closeCommand(cmd)) {
715 leavePara();
716 append(Atom::LegaleseRight);
717 }
718 break;
719 case CMD_ENDLINK:
720 if (closeCommand(cmd)) {
721 if (priv->text.lastAtom()->type() == Atom::String
722 && priv->text.lastAtom()->string().endsWith(" "))
723 priv->text.lastAtom()->chopString();
724 append(Atom::FormattingRight, ATOM_FORMATTING_LINK);
725 }
726 break;
727 case CMD_ENDLIST:
728 if (closeCommand(cmd)) {
729 leavePara();
730 if (openedLists.top().isStarted()) {
731 append(Atom::ListItemRight,
732 openedLists.top().styleString());
733 append(Atom::ListRight,
734 openedLists.top().styleString());
735 }
736 openedLists.pop();
737 }
738 break;
739 case CMD_ENDOMIT:
740 closeCommand(cmd);
741 break;
742 case CMD_ENDPART:
743 endSection(-1, cmd);
744 break;
745 case CMD_ENDQUOTATION:
746 if (closeCommand(cmd)) {
747 leavePara();
748 append(Atom::QuotationRight);
749 }
750 break;
751 case CMD_ENDRAW:
752 location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ENDRAW)));
753 break;
754 case CMD_ENDSECTION1:
755 endSection(1, cmd);
756 break;
757 case CMD_ENDSECTION2:
758 endSection(2, cmd);
759 break;
760 case CMD_ENDSECTION3:
761 endSection(3, cmd);
762 break;
763 case CMD_ENDSECTION4:
764 endSection(4, cmd);
765 break;
766 case CMD_ENDSIDEBAR:
767 if (closeCommand(cmd)) {
768 leavePara();
769 append(Atom::SidebarRight);
770 }
771 break;
772 case CMD_ENDTABLE:
773 if (closeCommand(cmd)) {
774 leaveTableRow();
775 append(Atom::TableRight);
776 }
777 break;
778 case CMD_EXPIRE:
779 checkExpiry(getArgument());
780 break;
781 case CMD_FOOTNOTE:
782 if (openCommand(cmd)) {
783 enterPara();
784 append(Atom::FootnoteLeft);
785 paraState = OutsidePara; // ###
786 }
787 break;
788 case CMD_GENERATELIST:
789 append(Atom::GeneratedList, getArgument());
790 break;
791 case CMD_GRANULARITY:
792 priv->constructExtra();
793 priv->extra->granularity = getSectioningUnit();
794 break;
795 case CMD_HEADER:
796 if (openedCommands.top() == CMD_TABLE) {
797 leaveTableRow();
798 append(Atom::TableHeaderLeft);
799 inTableHeader = true;
800 }
801 else {
802 if (openedCommands.contains(CMD_TABLE)) {
803 location().warning(tr("Cannot use '\\%1' within '\\%2'")
804 .arg(cmdName(CMD_HEADER))
805 .arg(cmdName(openedCommands.top())));
806 }
807 else {
808 location().warning(tr("Cannot use '\\%1' outside of '\\%2'")
809 .arg(cmdName(CMD_HEADER))
810 .arg(cmdName(CMD_TABLE)));
811 }
812 }
813 break;
814 case CMD_I:
815 startFormat(ATOM_FORMATTING_ITALIC, cmd);
816 break;
817 case CMD_IF:
818 preprocessorSkipping.push(!Tokenizer::isTrue(getRestOfLine()));
819 if (preprocessorSkipping.top())
820 ++numPreprocessorSkipping;
821 if (numPreprocessorSkipping)
822 skipToNextPreprocessorCommand();
823 break;
824 case CMD_IMAGE:
825 leaveValueList();
826 append(Atom::Image, getArgument());
827 append(Atom::ImageText, getRestOfLine());
828 break;
829 case CMD_INCLUDE:
830 include(getArgument());
831 break;
832 case CMD_INLINEIMAGE:
833 enterPara();
834 append(Atom::InlineImage, getArgument());
835 append(Atom::ImageText, getRestOfLine());
836 append(Atom::String, " ");
837 break;
838 case CMD_INDEX:
839 if (paraState == OutsidePara) {
840 enterPara();
841 indexStartedPara = true;
842 }
843 else {
844 const Atom *last = priv->text.lastAtom();
845 if (indexStartedPara &&
846 (last->type() != Atom::FormattingRight ||
847 last->string() != ATOM_FORMATTING_INDEX))
848 indexStartedPara = false;
849 }
850 startFormat(ATOM_FORMATTING_INDEX, cmd);
851 break;
852 case CMD_KEYWORD:
853 insertTarget(getRestOfLine(),true);
854 break;
855 case CMD_L:
856 enterPara();
857 if (isLeftBraceAhead()) {
858 x = getArgument();
859 append(Atom::Link, x);
860 if (isLeftBraceAhead()) {
861 currentLinkAtom = priv->text.lastAtom();
862 startFormat(ATOM_FORMATTING_LINK, cmd);
863 }
864 else {
865 append(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
866 append(Atom::String, cleanLink(x));
867 append(Atom::FormattingRight, ATOM_FORMATTING_LINK);
868 }
869 }
870 else {
871 x = getArgument();
872 append(Atom::Link, x);
873 append(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
874 append(Atom::String, cleanLink(x));
875 append(Atom::FormattingRight, ATOM_FORMATTING_LINK);
876 }
877 break;
878 case CMD_LEGALESE:
879 leavePara();
880 if (openCommand(cmd))
881 append(Atom::LegaleseLeft);
882 docPrivate->hasLegalese = true;
883 break;
884 case CMD_LINK:
885 if (openCommand(cmd)) {
886 enterPara();
887 x = getArgument();
888 append(Atom::Link, x);
889 append(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
890 skipSpacesOrOneEndl();
891 }
892 break;
893 case CMD_LIST:
894 if (openCommand(cmd)) {
895 leavePara();
896 openedLists.push(OpenedList(location(),
897 getOptionalArgument()));
898 }
899 break;
900 case CMD_META:
901 priv->constructExtra();
902 x = getArgument();
903 priv->extra->metaMap.insert(x, getRestOfLine());
904 break;
905 case CMD_NEWCODE:
906 location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_NEWCODE)));
907 break;
908 case CMD_O:
909 leavePara();
910 if (openedCommands.top() == CMD_LIST) {
911 if (openedLists.top().isStarted()) {
912 append(Atom::ListItemRight,
913 openedLists.top().styleString());
914 }
915 else {
916 append(Atom::ListLeft,
917 openedLists.top().styleString());
918 }
919 openedLists.top().next();
920 append(Atom::ListItemNumber,
921 openedLists.top().numberString());
922 append(Atom::ListItemLeft,
923 openedLists.top().styleString());
924 enterPara();
925 }
926 else if (openedCommands.top() == CMD_TABLE) {
927 x = "1,1";
928 if (isLeftBraceAhead())
929 x = getArgument();
930
931 if (!inTableHeader && !inTableRow) {
932 location().warning(tr("Missing '\\%1' or '\\%1' before '\\%3'")
933 .arg(cmdName(CMD_HEADER))
934 .arg(cmdName(CMD_ROW))
935 .arg(cmdName(CMD_O)));
936 append(Atom::TableRowLeft);
937 inTableRow = true;
938 }
939 else if (inTableItem) {
940 append(Atom::TableItemRight);
941 inTableItem = false;
942 }
943
944 append(Atom::TableItemLeft, x);
945 inTableItem = true;
946 }
947 else {
948 location().warning(tr("Command '\\%1' outside of '\\%2' and '\\%3'")
949 .arg(cmdName(cmd))
950 .arg(cmdName(CMD_LIST))
951 .arg(cmdName(CMD_TABLE)));
952 }
953 break;
954 case CMD_OLDCODE:
955 leavePara();
956#ifdef QDOC2DOX
957 if (DoxWriter::isDoxPass()) {
958 append(Atom::CodeOld, getUnmarkedCode(CMD_OLDCODE));
959 append(Atom::CodeNew, getUnmarkedCode(CMD_NEWCODE));
960 }
961 else {
962 append(Atom::CodeOld, getCode(CMD_OLDCODE, marker));
963 append(Atom::CodeNew, getCode(CMD_NEWCODE, marker));
964 }
965#else
966 append(Atom::CodeOld, getCode(CMD_OLDCODE, marker));
967 append(Atom::CodeNew, getCode(CMD_NEWCODE, marker));
968#endif
969 break;
970 case CMD_OMIT:
971 getUntilEnd(cmd);
972 break;
973 case CMD_OMITVALUE:
974 x = getArgument();
975 if (!priv->enumItemList.contains(x))
976 priv->enumItemList.append(x);
977 if (!priv->omitEnumItemList.contains(x))
978 priv->omitEnumItemList.append(x);
979 break;
980 case CMD_PART:
981 startSection(Doc::Part, cmd);
982 break;
983 case CMD_PRINTLINE:
984 leavePara();
985 if (!quoting)
986 appendToCode(quoter.quoteLine(location(), cmdStr,
987 getRestOfLine()));
988 else {
989 append(Atom::CodeQuoteCommand, cmdStr);
990 append(Atom::CodeQuoteArgument, getRestOfLine());
991 }
992 break;
993 case CMD_PRINTTO:
994 leavePara();
995 if (!quoting)
996 appendToCode(quoter.quoteTo(location(), cmdStr,
997 getRestOfLine()));
998 else {
999 append(Atom::CodeQuoteCommand, cmdStr);
1000 append(Atom::CodeQuoteArgument, getRestOfLine());
1001 }
1002 break;
1003 case CMD_PRINTUNTIL:
1004 leavePara();
1005 if (!quoting)
1006 appendToCode(quoter.quoteUntil(location(), cmdStr,
1007 getRestOfLine()));
1008 else {
1009 append(Atom::CodeQuoteCommand, cmdStr);
1010 append(Atom::CodeQuoteArgument, getRestOfLine());
1011 }
1012 break;
1013 case CMD_QUOTATION:
1014 if (openCommand(cmd)) {
1015 leavePara();
1016 append(Atom::QuotationLeft);
1017 }
1018 break;
1019 case CMD_QUOTEFILE:
1020 {
1021 leavePara();
1022 QString fileName = getArgument();
1023 Doc::quoteFromFile(location(), quoter, fileName);
1024 if (!quoting) {
1025 append(Atom::Code,
1026 quoter.quoteTo(location(), cmdStr, ""));
1027 quoter.reset();
1028 }
1029 else {
1030 append(Atom::CodeQuoteCommand, cmdStr);
1031 append(Atom::CodeQuoteArgument, fileName);
1032 }
1033 break;
1034 }
1035 case CMD_QUOTEFROMFILE:
1036 leavePara();
1037 if (!quoting)
1038 quoteFromFile();
1039 else {
1040 append(Atom::CodeQuoteCommand, cmdStr);
1041 append(Atom::CodeQuoteArgument, getArgument());
1042 }
1043 break;
1044 case CMD_QUOTEFUNCTION:
1045 leavePara();
1046 marker = quoteFromFile();
1047 x = getRestOfLine();
1048 if (!quoting) {
1049 quoter.quoteTo(location(), cmdStr,
1050 slashed(marker->functionBeginRegExp(x)));
1051 append(Atom::Code,
1052 quoter.quoteUntil(location(), cmdStr,
1053 slashed(marker->functionEndRegExp(x))));
1054 quoter.reset();
1055 }
1056 else {
1057 append(Atom::CodeQuoteCommand, cmdStr);
1058 append(Atom::CodeQuoteArgument, slashed(marker->functionEndRegExp(x)));
1059 }
1060 break;
1061 case CMD_RAW:
1062 leavePara();
1063 x = getRestOfLine();
1064 if (x.isEmpty())
1065 location().warning(tr("Missing format name after '\\%1")
1066 .arg(cmdName(CMD_RAW)));
1067 append(Atom::FormatIf, x);
1068 append(Atom::RawString, untabifyEtc(getUntilEnd(cmd)));
1069 append(Atom::FormatElse);
1070 append(Atom::FormatEndif);
1071 break;
1072 case CMD_ROW:
1073 if (openedCommands.top() == CMD_TABLE) {
1074 leaveTableRow();
1075 append(Atom::TableRowLeft);
1076 inTableRow = true;
1077 }
1078 else {
1079 if (openedCommands.contains(CMD_TABLE)) {
1080 location().warning(tr("Cannot use '\\%1' within '\\%2'")
1081 .arg(cmdName(CMD_ROW))
1082 .arg(cmdName(openedCommands.top())));
1083 }
1084 else {
1085 location().warning(tr("Cannot use '\\%1' outside of '\\%2'")
1086 .arg(cmdName(CMD_ROW))
1087 .arg(cmdName(CMD_TABLE)));
1088 }
1089 }
1090 break;
1091 case CMD_SA:
1092 parseAlso();
1093 break;
1094 case CMD_SECTION1:
1095 startSection(Doc::Section1, cmd);
1096 break;
1097 case CMD_SECTION2:
1098 startSection(Doc::Section2, cmd);
1099 break;
1100 case CMD_SECTION3:
1101 startSection(Doc::Section3, cmd);
1102 break;
1103 case CMD_SECTION4:
1104 startSection(Doc::Section4, cmd);
1105 break;
1106 case CMD_SIDEBAR:
1107 if (openCommand(cmd)) {
1108 leavePara();
1109 append(Atom::SidebarLeft);
1110 }
1111 break;
1112 case CMD_SKIPLINE:
1113 leavePara();
1114 if (!quoting)
1115 quoter.quoteLine(location(),
1116 cmdStr,
1117 getRestOfLine());
1118 else {
1119 append(Atom::CodeQuoteCommand, cmdStr);
1120 append(Atom::CodeQuoteArgument, getRestOfLine());
1121 }
1122 break;
1123 case CMD_SKIPTO:
1124 leavePara();
1125 if (!quoting)
1126 quoter.quoteTo(location(),
1127 cmdStr,
1128 getRestOfLine());
1129 else {
1130 append(Atom::CodeQuoteCommand, cmdStr);
1131 append(Atom::CodeQuoteArgument, getRestOfLine());
1132 }
1133 break;
1134 case CMD_SKIPUNTIL:
1135 leavePara();
1136 if (!quoting)
1137 quoter.quoteUntil(location(),
1138 cmdStr,
1139 getRestOfLine());
1140 else {
1141 append(Atom::CodeQuoteCommand, cmdStr);
1142 append(Atom::CodeQuoteArgument, getRestOfLine());
1143 }
1144 break;
1145 case CMD_SNIPPET:
1146 leavePara();
1147 {
1148 QString snippet = getArgument();
1149 QString identifier = getRestOfLine();
1150#ifdef QDOC2DOX
1151 if (quoting || DoxWriter::isDoxPass()) {
1152 append(Atom::SnippetCommand, cmdStr);
1153 append(Atom::SnippetLocation, snippet);
1154 append(Atom::SnippetIdentifier, identifier);
1155 }
1156 else {
1157 Doc::quoteFromFile(location(),quoter,snippet);
1158 appendToCode(quoter.quoteSnippet(location(),
1159 identifier));
1160 }
1161#else
1162 if (quoting) {
1163 append(Atom::SnippetCommand, cmdStr);
1164 append(Atom::SnippetLocation, snippet);
1165 append(Atom::SnippetIdentifier, identifier);
1166 }
1167 else {
1168 Doc::quoteFromFile(location(),quoter,snippet);
1169 appendToCode(quoter.quoteSnippet(location(),
1170 identifier));
1171 }
1172#endif
1173 }
1174 break;
1175 case CMD_SUB:
1176 startFormat(ATOM_FORMATTING_SUBSCRIPT, cmd);
1177 break;
1178 case CMD_SUP:
1179 startFormat(ATOM_FORMATTING_SUPERSCRIPT, cmd);
1180 break;
1181 case CMD_TABLE:
1182 x = getRestOfLine();
1183 if (openCommand(cmd)) {
1184 leavePara();
1185 append(Atom::TableLeft, x);
1186 inTableHeader = false;
1187 inTableRow = false;
1188 inTableItem = false;
1189 }
1190 break;
1191 case CMD_TABLEOFCONTENTS:
1192 x = "1";
1193 if (isLeftBraceAhead())
1194 x = getArgument();
1195 x += ",";
1196 x += QString::number((int)getSectioningUnit());
1197 append(Atom::TableOfContents, x);
1198 break;
1199 case CMD_TARGET:
1200 insertTarget(getRestOfLine(),false);
1201 break;
1202 case CMD_TT:
1203 startFormat(ATOM_FORMATTING_TELETYPE, cmd);
1204 break;
1205 case CMD_UNDERLINE:
1206 startFormat(ATOM_FORMATTING_UNDERLINE, cmd);
1207 break;
1208 case CMD_UNICODE:
1209 enterPara();
1210 x = getArgument();
1211 {
1212 bool ok;
1213 uint unicodeChar = x.toUInt(&ok, 0);
1214 if (!ok ||
1215 (unicodeChar == 0x0000) ||
1216 (unicodeChar > 0xFFFE)) {
1217 location().warning(tr("Invalid Unicode character '%1' specified "
1218 "with '%2'")
1219 .arg(x, cmdName(CMD_UNICODE)));
1220 }
1221 else {
1222 append(Atom::String, QChar(unicodeChar));
1223 }
1224 }
1225 break;
1226 case CMD_VALUE:
1227 leaveValue();
1228 if (openedLists.top().style() == OpenedList::Value) {
1229 x = getArgument();
1230 if (!priv->enumItemList.contains(x))
1231 priv->enumItemList.append(x);
1232
1233 openedLists.top().next();
1234 append(Atom::ListTagLeft, ATOM_LIST_VALUE);
1235 append(Atom::String, x);
1236 append(Atom::ListTagRight, ATOM_LIST_VALUE);
1237 append(Atom::ListItemLeft, ATOM_LIST_VALUE);
1238
1239 skipSpacesOrOneEndl();
1240 if (isBlankLine())
1241 append(Atom::Nop);
1242 }
1243 else {
1244 // ### problems
1245 }
1246 break;
1247 case CMD_WARNING:
1248 startNewPara();
1249 append(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1250 append(Atom::String, "Warning:");
1251 append(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1252 append(Atom::String, " ");
1253 break;
1254 case CMD_OVERLOAD: // qdoc --> doxygen
1255 priv->metacommandsUsed.insert(cmdStr);
1256 x.clear();
1257 if (!isBlankLine())
1258 x = getRestOfLine();
1259 if (!x.isEmpty()) {
1260 append(Atom::ParaLeft);
1261 append(Atom::String, "This function overloads ");
1262 append(Atom::AutoLink,x);
1263 append(Atom::String, ".");
1264 append(Atom::ParaRight);
1265 }
1266 else {
1267 append(Atom::ParaLeft);
1268 append(Atom::String,
1269 "This is an overloaded member function, "
1270 "provided for convenience.");
1271 append(Atom::ParaRight);
1272 x = getMetaCommandArgument(cmdStr);
1273 }
1274 priv->metaCommandMap[cmdStr].append(x);
1275 break;
1276 case NOT_A_CMD:
1277 if (metaCommandSet.contains(cmdStr)) {
1278 priv->metacommandsUsed.insert(cmdStr);
1279 QString xxx = getMetaCommandArgument(cmdStr);
1280 priv->metaCommandMap[cmdStr].append(xxx);
1281 }
1282 else if (macroHash()->contains(cmdStr)) {
1283 const Macro &macro = macroHash()->value(cmdStr);
1284 int numPendingFi = 0;
1285 QStringMap::ConstIterator d;
1286 d = macro.otherDefs.begin();
1287 while (d != macro.otherDefs.end()) {
1288 append(Atom::FormatIf, d.key());
1289 expandMacro(cmdStr, *d, macro.numParams);
1290 ++d;
1291
1292 if (d == macro.otherDefs.end()) {
1293 append(Atom::FormatEndif);
1294 }
1295 else {
1296 append(Atom::FormatElse);
1297 numPendingFi++;
1298 }
1299 }
1300 while (numPendingFi-- > 0)
1301 append(Atom::FormatEndif);
1302
1303 if (!macro.defaultDef.isEmpty()) {
1304 if (!macro.otherDefs.isEmpty()) {
1305 macro.defaultDefLocation.warning(
1306 tr("Macro cannot have both "
1307 "format-specific and qdoc- "
1308 "syntax definitions"));
1309 }
1310 else {
1311 location().push(macro.defaultDefLocation.filePath());
1312 in.insert(pos, macro.defaultDef);
1313 len = in.length();
1314 openedInputs.push(pos + macro.defaultDef.length());
1315 }
1316 }
1317 }
1318 else {
1319 location().warning(
1320 tr("Unknown command '\\%1'").arg(cmdStr),
1321 detailsUnknownCommand(metaCommandSet,cmdStr));
1322 enterPara();
1323 append(Atom::UnknownCommand, cmdStr);
1324 }
1325 }
1326 }
1327 }
1328 break;
1329 case '{':
1330 enterPara();
1331 appendChar('{');
1332 braceDepth++;
1333 pos++;
1334 break;
1335 case '}':
1336 {
1337 braceDepth--;
1338 pos++;
1339
1340 QMap<int, QString>::Iterator f =
1341 pendingFormats.find(braceDepth);
1342 if (f == pendingFormats.end()) {
1343 enterPara();
1344 appendChar('}');
1345 }
1346 else {
1347 append(Atom::FormattingRight, *f);
1348 if (*f == ATOM_FORMATTING_INDEX) {
1349 if (indexStartedPara)
1350 skipAllSpaces();
1351 }
1352 else if (*f == ATOM_FORMATTING_LINK) {
1353 // hack for C++ to support links like
1354 // \l{QString::}{count()}
1355 if (currentLinkAtom &&
1356 currentLinkAtom->string().endsWith("::")) {
1357 QString suffix = Text::subText(currentLinkAtom,
1358 priv->text.lastAtom()).toString();
1359 currentLinkAtom->appendString(suffix);
1360 }
1361 currentLinkAtom = 0;
1362 }
1363 pendingFormats.erase(f);
1364 }
1365 }
1366 break;
1367 default:
1368 {
1369 bool newWord;
1370 switch (priv->text.lastAtom()->type()) {
1371 case Atom::ParaLeft:
1372 newWord = true;
1373 break;
1374 default:
1375 newWord = false;
1376 }
1377
1378 if (paraState == OutsidePara) {
1379 if (ch.isSpace()) {
1380 ++pos;
1381 newWord = false;
1382 }
1383 else {
1384 enterPara();
1385 newWord = true;
1386 }
1387 }
1388 else {
1389 if (ch.isSpace()) {
1390 ++pos;
1391 if ((ch == '\n') &&
1392 (paraState == InsideSingleLinePara ||
1393 isBlankLine())) {
1394 leavePara();
1395 newWord = false;
1396 }
1397 else {
1398 appendChar(' ');
1399 newWord = true;
1400 }
1401 }
1402 else {
1403 newWord = true;
1404 }
1405 }
1406
1407 if (newWord) {
1408 int startPos = pos;
1409 int numInternalUppercase = 0;
1410 int numLowercase = 0;
1411 int numStrangeSymbols = 0;
1412
1413 while (pos < len) {
1414 unsigned char latin1Ch = in.at(pos).toLatin1();
1415 if (islower(latin1Ch)) {
1416 ++numLowercase;
1417 ++pos;
1418 }
1419 else if (isupper(latin1Ch)) {
1420 if (pos > startPos)
1421 ++numInternalUppercase;
1422 ++pos;
1423 }
1424 else if (isdigit(latin1Ch)) {
1425 if (pos > startPos) {
1426 ++pos;
1427 }
1428 else {
1429 break;
1430 }
1431 }
1432 else if (latin1Ch == '_' || latin1Ch == '@') {
1433 ++numStrangeSymbols;
1434 ++pos;
1435 }
1436 else if (latin1Ch == ':' && pos < len - 1
1437 && in.at(pos + 1) == QLatin1Char(':')) {
1438 ++numStrangeSymbols;
1439 pos += 2;
1440 }
1441 else if (latin1Ch == '(') {
1442 if (pos > startPos) {
1443 if (pos < len - 1 &&
1444 in.at(pos + 1) == QLatin1Char(')')) {
1445 ++numStrangeSymbols;
1446 pos += 2;
1447 break;
1448 }
1449 else {
1450 // ### handle functions with signatures
1451 // and function calls
1452 break;
1453 }
1454 }
1455 else {
1456 break;
1457 }
1458 }
1459 else {
1460 break;
1461 }
1462 }
1463
1464 if (pos == startPos) {
1465 if (!ch.isSpace()) {
1466 appendChar(ch);
1467 ++pos;
1468 }
1469 }
1470 else {
1471 QString word = in.mid(startPos, pos - startPos);
1472 // is word a C++ symbol or an English word?
1473 if ((numInternalUppercase >= 1 && numLowercase >= 2)
1474 || numStrangeSymbols >= 1) {
1475 append(Atom::AutoLink, word);
1476 }
1477 else {
1478 appendWord(word);
1479 }
1480 }
1481 }
1482 }
1483 }
1484 }
1485 leaveValueList();
1486
1487 // for compatibility
1488 if (openedCommands.top() == CMD_LEGALESE) {
1489 append(Atom::LegaleseRight);
1490 openedCommands.pop();
1491 }
1492
1493 if (openedCommands.top() != CMD_OMIT) {
1494 location().warning(tr("Missing '\\%1'").arg(endCmdName(openedCommands.top())));
1495 }
1496 else if (preprocessorSkipping.count() > 0) {
1497 location().warning(tr("Missing '\\%1'").arg(cmdName(CMD_ENDIF)));
1498 }
1499
1500 while (currentSectioningUnit > Doc::Chapter) {
1501 int delta = currentSectioningUnit - priv->extra->sectioningUnit;
1502 append(Atom::SectionRight, QString::number(delta));
1503 currentSectioningUnit = Doc::SectioningUnit(int(currentSectioningUnit) - 1);
1504 }
1505
1506 if (priv->extra && priv->extra->granularity < priv->extra->sectioningUnit)
1507 priv->extra->granularity = priv->extra->sectioningUnit;
1508 priv->text.stripFirstAtom();
1509}
1510
1511Location &DocParser::location()
1512{
1513 while (!openedInputs.isEmpty() && openedInputs.top() <= pos) {
1514 cachedLoc.pop();
1515 cachedPos = openedInputs.pop();
1516 }
1517 while (cachedPos < pos)
1518 cachedLoc.advance(in.at(cachedPos++));
1519 return cachedLoc;
1520}
1521
1522QString DocParser::detailsUnknownCommand(const QSet<QString> &metaCommandSet,
1523 const QString &str)
1524{
1525 QSet<QString> commandSet = metaCommandSet;
1526 int i = 0;
1527 while (cmds[i].english != 0) {
1528 commandSet.insert(*cmds[i].alias);
1529 i++;
1530 }
1531
1532 if (aliasMap()->contains(str))
1533 return tr("The command '\\%1' was renamed '\\%2' by the configuration"
1534 " file. Use the new name.")
1535 .arg(str).arg((*aliasMap())[str]);
1536
1537 QString best = nearestName(str, commandSet);
1538 if (best.isEmpty())
1539 return QString();
1540 return tr("Maybe you meant '\\%1'?").arg(best);
1541}
1542
1543void DocParser::checkExpiry(const QString& date)
1544{
1545 QRegExp ymd("(\\d{4})(?:-(\\d{2})(?:-(\\d{2})))");
1546
1547 if (ymd.exactMatch(date)) {
1548 int y = ymd.cap(1).toInt();
1549 int m = ymd.cap(2).toInt();
1550 int d = ymd.cap(3).toInt();
1551
1552 if (m == 0)
1553 m = 1;
1554 if (d == 0)
1555 d = 1;
1556 QDate expiryDate(y, m, d);
1557 if (expiryDate.isValid()) {
1558 int days = expiryDate.daysTo(QDate::currentDate());
1559 if (days == 0) {
1560 location().warning(tr("Documentation expires today"));
1561 }
1562 else if (days == 1) {
1563 location().warning(tr("Documentation expired yesterday"));
1564 }
1565 else if (days >= 2) {
1566 location().warning(tr("Documentation expired %1 days ago")
1567 .arg(days));
1568 }
1569 }
1570 else {
1571 location().warning(tr("Date '%1' invalid").arg(date));
1572 }
1573 }
1574 else {
1575 location().warning(tr("Date '%1' not in YYYY-MM-DD format")
1576 .arg(date));
1577 }
1578}
1579
1580void DocParser::insertBaseName(const QString &baseName)
1581{
1582 priv->constructExtra();
1583 if (currentSectioningUnit == priv->extra->sectioningUnit) {
1584 priv->extra->baseName = baseName;
1585 }
1586 else {
1587 Atom *atom = priv->text.firstAtom();
1588 Atom *sectionLeft = 0;
1589
1590 int delta = currentSectioningUnit - priv->extra->sectioningUnit;
1591
1592 while (atom != 0) {
1593 if (atom->type() == Atom::SectionLeft &&
1594 atom->string().toInt() == delta)
1595 sectionLeft = atom;
1596 atom = atom->next();
1597 }
1598 if (sectionLeft != 0)
1599 (void) new Atom(sectionLeft, Atom::BaseName, baseName);
1600 }
1601}
1602
1603void DocParser::insertTarget(const QString &target, bool keyword)
1604{
1605 if (targetMap.contains(target)) {
1606 location().warning(tr("Duplicate target name '%1'").arg(target));
1607 targetMap[target].warning(tr("(The previous occurrence is here)"));
1608 }
1609 else {
1610 targetMap.insert(target, location());
1611 append(Atom::Target, target);
1612 priv->constructExtra();
1613 if (keyword)
1614 priv->extra->keywords.append(priv->text.lastAtom());
1615 else
1616 priv->extra->targets.append(priv->text.lastAtom());
1617 }
1618}
1619
1620void DocParser::include(const QString& fileName)
1621{
1622 if (location().depth() > 16)
1623 location().fatal(tr("Too many nested '\\%1's")
1624 .arg(cmdName(CMD_INCLUDE)));
1625
1626 QString userFriendlyFilePath;
1627 // ### use current directory?
1628 QString filePath = Config::findFile(location(),
1629 sourceFiles,
1630 sourceDirs,
1631 fileName,
1632 userFriendlyFilePath);
1633 if (filePath.isEmpty()) {
1634 location().warning(tr("Cannot find leaf file '%1'").arg(fileName));
1635 }
1636 else {
1637 QFile inFile(filePath);
1638 if (!inFile.open(QFile::ReadOnly)) {
1639 location().warning(tr("Cannot open leaf file '%1'")
1640 .arg(userFriendlyFilePath));
1641 }
1642 else {
1643 location().push(userFriendlyFilePath);
1644
1645 QTextStream inStream(&inFile);
1646 QString includedStuff = inStream.readAll();
1647 inFile.close();
1648
1649 in.insert(pos, includedStuff);
1650 len = in.length();
1651 openedInputs.push(pos + includedStuff.length());
1652 }
1653 }
1654}
1655
1656void DocParser::startFormat(const QString& format, int cmd)
1657{
1658 enterPara();
1659
1660 QMap<int, QString>::ConstIterator f = pendingFormats.begin();
1661 while (f != pendingFormats.end()) {
1662 if (*f == format) {
1663 location().warning(tr("Cannot nest '\\%1' commands")
1664 .arg(cmdName(cmd)));
1665 return;
1666 }
1667 ++f;
1668 }
1669
1670 append(Atom::FormattingLeft, format);
1671
1672 if (isLeftBraceAhead()) {
1673 skipSpacesOrOneEndl();
1674 pendingFormats.insert(braceDepth, format);
1675 ++braceDepth;
1676 ++pos;
1677 }
1678 else {
1679 append(Atom::String, getArgument());
1680 append(Atom::FormattingRight, format);
1681 if (format == ATOM_FORMATTING_INDEX && indexStartedPara) {
1682 skipAllSpaces();
1683 indexStartedPara = false;
1684 }
1685 }
1686}
1687
1688bool DocParser::openCommand(int cmd)
1689{
1690 int outer = openedCommands.top();
1691 bool ok = true;
1692
1693 if (cmd != CMD_LINK) {
1694 if (outer == CMD_LIST) {
1695 ok = (cmd == CMD_FOOTNOTE || cmd == CMD_LIST);
1696 }
1697 else if (outer == CMD_ABSTRACT) {
1698 ok = (cmd == CMD_LIST ||
1699 cmd == CMD_QUOTATION ||
1700 cmd == CMD_TABLE);
1701 }
1702 else if (outer == CMD_SIDEBAR) {
1703 ok = (cmd == CMD_LIST ||
1704 cmd == CMD_QUOTATION ||
1705 cmd == CMD_SIDEBAR);
1706 }
1707 else if (outer == CMD_QUOTATION) {
1708 ok = (cmd == CMD_LIST);
1709 }
1710 else if (outer == CMD_TABLE) {
1711 ok = (cmd == CMD_LIST ||
1712 cmd == CMD_FOOTNOTE ||
1713 cmd == CMD_QUOTATION);
1714 }
1715 else if (outer == CMD_FOOTNOTE || outer == CMD_LINK) {
1716 ok = false;
1717 }
1718 }
1719
1720 if (ok) {
1721 openedCommands.push(cmd);
1722 }
1723 else {
1724 location().warning(tr("Cannot use '\\%1' within '\\%2'")
1725 .arg(cmdName(cmd)).arg(cmdName(outer)));
1726 }
1727 return ok;
1728}
1729
1730bool DocParser::closeCommand(int endCmd)
1731{
1732 if (endCmdFor(openedCommands.top()) == endCmd && openedCommands.size() > 1) {
1733 openedCommands.pop();
1734 return true;
1735 }
1736 else {
1737 bool contains = false;
1738 QStack<int> opened2 = openedCommands;
1739 while (opened2.size() > 1) {
1740 if (endCmdFor(opened2.top()) == endCmd) {
1741 contains = true;
1742 break;
1743 }
1744 opened2.pop();
1745 }
1746
1747 if (contains) {
1748 while (endCmdFor(openedCommands.top()) != endCmd && openedCommands.size() > 1) {
1749 location().warning(tr("Missing '\\%1' before '\\%2'")
1750 .arg(endCmdName(openedCommands.top()))
1751 .arg(cmdName(endCmd)));
1752 openedCommands.pop();
1753 }
1754 }
1755 else {
1756 location().warning(tr("Unexpected '\\%1'")
1757 .arg(cmdName(endCmd)));
1758 }
1759 return false;
1760 }
1761}
1762
1763void DocParser::startSection(Doc::SectioningUnit unit, int cmd)
1764{
1765 leavePara();
1766
1767 if (currentSectioningUnit == Doc::Book) {
1768 if (unit > Doc::Section1)
1769 location().warning(tr("Unexpected '\\%1' without '\\%2'")
1770 .arg(cmdName(cmd))
1771 .arg(cmdName(CMD_SECTION1)));
1772 currentSectioningUnit = (Doc::SectioningUnit) (unit - 1);
1773 priv->constructExtra();
1774 priv->extra->sectioningUnit = currentSectioningUnit;
1775 }
1776
1777 if (unit <= priv->extra->sectioningUnit) {
1778 location().warning(tr("Unexpected '\\%1' in this documentation")
1779 .arg(cmdName(cmd)));
1780 }
1781 else if (unit - currentSectioningUnit > 1) {
1782 location().warning(tr("Unexpected '\\%1' at this point")
1783 .arg(cmdName(cmd)));
1784 }
1785 else {
1786 if (currentSectioningUnit >= unit)
1787 endSection(unit, cmd);
1788
1789 int delta = unit - priv->extra->sectioningUnit;
1790 append(Atom::SectionLeft, QString::number(delta));
1791 priv->constructExtra();
1792 priv->extra->tableOfContents.append(priv->text.lastAtom());
1793 priv->extra->tableOfContentsLevels.append(unit);
1794 enterPara(Atom::SectionHeadingLeft,
1795 Atom::SectionHeadingRight,
1796 QString::number(delta));
1797 currentSectioningUnit = unit;
1798 }
1799}
1800
1801void DocParser::endSection(int unit, int endCmd)
1802{
1803 leavePara();
1804
1805 if (unit < priv->extra->sectioningUnit) {
1806 location().warning(tr("Unexpected '\\%1' in this documentation")
1807 .arg(cmdName(endCmd)));
1808 }
1809 else if (unit > currentSectioningUnit) {
1810 location().warning(tr("Unexpected '\\%1' at this point")
1811 .arg(cmdName(endCmd)));
1812 }
1813 else {
1814 while (currentSectioningUnit >= unit) {
1815 int delta = currentSectioningUnit - priv->extra->sectioningUnit;
1816 append(Atom::SectionRight, QString::number(delta));
1817 currentSectioningUnit =
1818 (Doc::SectioningUnit) (currentSectioningUnit - 1);
1819 }
1820 }
1821}
1822
1823void DocParser::parseAlso()
1824{
1825 leavePara();
1826 skipSpacesOnLine();
1827 while (pos < len && in[pos] != '\n') {
1828 QString target;
1829 QString str;
1830
1831 if (in[pos] == '{') {
1832 target = getArgument();
1833 skipSpacesOnLine();
1834 if (in[pos] == '{') {
1835 str = getArgument();
1836
1837 // hack for C++ to support links like \l{QString::}{count()}
1838 if (target.endsWith("::"))
1839 target += str;
1840 }
1841 else {
1842 str = target;
1843 }
1844#ifdef QDOC2_COMPAT
1845 }
1846 else if (in[pos] == '\\' && in.mid(pos, 5) == "\\link") {
1847 pos += 6;
1848 target = getArgument();
1849 int endPos = in.indexOf("\\endlink", pos);
1850 if (endPos != -1) {
1851 str = in.mid(pos, endPos - pos).trimmed();
1852 pos = endPos + 8;
1853 }
1854#endif
1855 }
1856 else {
1857 target = getArgument();
1858 str = cleanLink(target);
1859 }
1860
1861 Text also;
1862 also << Atom(Atom::Link, target)
1863 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1864 << str
1865 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1866 priv->addAlso(also);
1867
1868 skipSpacesOnLine();
1869 if (pos < len && in[pos] == ',') {
1870 pos++;
1871 skipSpacesOrOneEndl();
1872 }
1873 else if (in[pos] != '\n') {
1874 location().warning(tr("Missing comma in '\\%1'").arg(cmdName(CMD_SA)));
1875 }
1876 }
1877}
1878
1879void DocParser::append(Atom::Type type, const QString &string)
1880{
1881 Atom::Type lastType = priv->text.lastAtom()->type();
1882#ifdef QDOC_QML
1883 if (((lastType == Atom::Code) || (lastType == Atom::Code)) &&
1884#else
1885 if ((lastType == Atom::Code) &&
1886#endif
1887 priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n")))
1888 priv->text.lastAtom()->chopString();
1889 priv->text << Atom(type, string);
1890}
1891
1892void DocParser::appendChar(QChar ch)
1893{
1894 if (priv->text.lastAtom()->type() != Atom::String)
1895 append(Atom::String);
1896 Atom *atom = priv->text.lastAtom();
1897 if (ch == QLatin1Char(' ')) {
1898 if (!atom->string().endsWith(QLatin1Char(' ')))
1899 atom->appendChar(QLatin1Char(' '));
1900 }
1901 else
1902 atom->appendChar(ch);
1903}
1904
1905void DocParser::appendWord(const QString &word)
1906{
1907 if (priv->text.lastAtom()->type() != Atom::String) {
1908 append(Atom::String, word);
1909 }
1910 else
1911 priv->text.lastAtom()->appendString(word);
1912}
1913
1914void DocParser::appendToCode(const QString& markedCode)
1915{
1916 Atom::Type lastType = priv->text.lastAtom()->type();
1917#ifdef QDOC_QML
1918 if (lastType != Atom::Qml)
1919 append(Atom::Qml);
1920#else
1921 if (lastType != Atom::Code)
1922 append(Atom::Code);
1923#endif
1924 priv->text.lastAtom()->appendString(markedCode);
1925}
1926
1927void DocParser::startNewPara()
1928{
1929 leavePara();
1930 enterPara();
1931}
1932
1933void DocParser::enterPara(Atom::Type leftType,
1934 Atom::Type rightType,
1935 const QString& string)
1936{
1937 if (paraState == OutsidePara) {
1938 if (priv->text.lastAtom()->type() != Atom::ListItemLeft)
1939 leaveValueList();
1940 append(leftType, string);
1941 indexStartedPara = false;
1942 pendingParaLeftType = leftType;
1943 pendingParaRightType = rightType;
1944 pendingParaString = string;
1945 if (
1946#if 0
1947 leftType == Atom::BriefLeft ||
1948#endif
1949 leftType == Atom::SectionHeadingLeft) {
1950 paraState = InsideSingleLinePara;
1951 }
1952 else {
1953 paraState = InsideMultiLinePara;
1954 }
1955 skipSpacesOrOneEndl();
1956 }
1957}
1958
1959void DocParser::leavePara()
1960{
1961 if (paraState != OutsidePara) {
1962 if (!pendingFormats.isEmpty()) {
1963 location().warning(tr("Missing '}'"));
1964 pendingFormats.clear();
1965 }
1966
1967 if (priv->text.lastAtom()->type() == pendingParaLeftType) {
1968 priv->text.stripLastAtom();
1969 }
1970 else {
1971 if (priv->text.lastAtom()->type() == Atom::String &&
1972 priv->text.lastAtom()->string().endsWith(" ")) {
1973 priv->text.lastAtom()->chopString();
1974 }
1975 append(pendingParaRightType, pendingParaString);
1976 }
1977 paraState = OutsidePara;
1978 indexStartedPara = false;
1979 pendingParaRightType = Atom::Nop;
1980 pendingParaString = "";
1981 }
1982}
1983
1984void DocParser::leaveValue()
1985{
1986 leavePara();
1987 if (openedLists.isEmpty()) {
1988 openedLists.push(OpenedList(OpenedList::Value));
1989 append(Atom::ListLeft, ATOM_LIST_VALUE);
1990 }
1991 else {
1992 if (priv->text.lastAtom()->type() == Atom::Nop)
1993 priv->text.stripLastAtom();
1994 append(Atom::ListItemRight, ATOM_LIST_VALUE);
1995 }
1996}
1997
1998void DocParser::leaveValueList()
1999{
2000 leavePara();
2001 if (!openedLists.isEmpty() &&
2002 (openedLists.top().style() == OpenedList::Value)) {
2003 if (priv->text.lastAtom()->type() == Atom::Nop)
2004 priv->text.stripLastAtom();
2005 append(Atom::ListItemRight, ATOM_LIST_VALUE);
2006 append(Atom::ListRight, ATOM_LIST_VALUE);
2007 openedLists.pop();
2008 }
2009}
2010
2011void DocParser::leaveTableRow()
2012{
2013 if (inTableItem) {
2014 leavePara();
2015 append(Atom::TableItemRight);
2016 inTableItem = false;
2017 }
2018 if (inTableHeader) {
2019 append(Atom::TableHeaderRight);
2020 inTableHeader = false;
2021 }
2022 if (inTableRow) {
2023 append(Atom::TableRowRight);
2024 inTableRow = false;
2025 }
2026}
2027
2028CodeMarker *DocParser::quoteFromFile()
2029{
2030 return Doc::quoteFromFile(location(), quoter, getArgument());
2031}
2032
2033void DocParser::expandMacro(const QString &name,
2034 const QString &def,
2035 int numParams)
2036{
2037 if (numParams == 0) {
2038 append(Atom::RawString, def);
2039 }
2040 else {
2041 QStringList args;
2042 QString rawString;
2043
2044 for (int i = 0; i < numParams; i++) {
2045 if (numParams == 1 || isLeftBraceAhead()) {
2046 args << getArgument(true);
2047 }
2048 else {
2049 location().warning(tr("Macro '\\%1' invoked with too few"
2050 " arguments (expected %2, got %3)")
2051 .arg(name).arg(numParams).arg(i));
2052 break;
2053 }
2054 }
2055
2056 int j = 0;
2057 while (j < def.size()) {
2058 int paramNo;
2059 if ((def[j] == '\\') && (j < def.size() - 1) &&
2060 ((paramNo = def[j + 1].digitValue()) >= 1) &&
2061 (paramNo <= numParams)) {
2062 if (!rawString.isEmpty()) {
2063 append(Atom::RawString, rawString);
2064 rawString = "";
2065 }
2066 append(Atom::String, args[paramNo - 1]);
2067 j += 2;
2068 }
2069 else {
2070 rawString += def[j++];
2071 }
2072 }
2073 if (!rawString.isEmpty())
2074 append(Atom::RawString, rawString);
2075 }
2076}
2077
2078Doc::SectioningUnit DocParser::getSectioningUnit()
2079{
2080 QString name = getOptionalArgument();
2081
2082 if (name == "part") {
2083 return Doc::Part;
2084 }
2085 else if (name == "chapter") {
2086 return Doc::Chapter;
2087 }
2088 else if (name == "section1") {
2089 return Doc::Section1;
2090 }
2091 else if (name == "section2") {
2092 return Doc::Section2;
2093 }
2094 else if (name == "section3") {
2095 return Doc::Section3;
2096 }
2097 else if (name == "section4") {
2098 return Doc::Section4;
2099 }
2100 else if (name.isEmpty()) {
2101 return Doc::Section4;
2102 }
2103 else {
2104 location().warning(tr("Invalid sectioning unit '%1'").arg(name));
2105 return Doc::Book;
2106 }
2107}
2108
2109QString DocParser::getArgument(bool verbatim)
2110{
2111 QString arg;
2112 int delimDepth = 0;
2113
2114 skipSpacesOrOneEndl();
2115
2116 int startPos = pos;
2117
2118 /*
2119 Typically, an argument ends at the next white-space. However,
2120 braces can be used to group words:
2121
2122 {a few words}
2123
2124 Also, opening and closing parentheses have to match. Thus,
2125
2126 printf("%d\n", x)
2127
2128 is an argument too, although it contains spaces. Finally,
2129 trailing punctuation is not included in an argument, nor is 's.
2130 */
2131 if (pos < (int) in.length() && in[pos] == '{') {
2132 pos++;
2133 while (pos < (int) in.length() && delimDepth >= 0) {
2134 switch (in[pos].unicode()) {
2135 case '{':
2136 delimDepth++;
2137 arg += "{";
2138 pos++;
2139 break;
2140 case '}':
2141 delimDepth--;
2142 if (delimDepth >= 0)
2143 arg += "}";
2144 pos++;
2145 break;
2146 case '\\':
2147 if (verbatim) {
2148 arg += in[pos];
2149 pos++;
2150 }
2151 else {
2152 pos++;
2153 if (pos < (int) in.length()) {
2154 if (in[pos].isLetterOrNumber())
2155 break;
2156 arg += in[pos];
2157 if (in[pos].isSpace()) {
2158 skipAllSpaces();
2159 }
2160 else {
2161 pos++;
2162 }
2163 }
2164 }
2165 break;
2166 default:
2167 arg += in[pos];
2168 pos++;
2169 }
2170 }
2171 if (delimDepth > 0)
2172 location().warning(tr("Missing '}'"));
2173 }
2174 else {
2175 while (pos < in.length() &&
2176 ((delimDepth > 0) ||
2177 ((delimDepth == 0) &&
2178 !in[pos].isSpace()))) {
2179 switch (in[pos].unicode()) {
2180 case '(':
2181 case '[':
2182 case '{':
2183 delimDepth++;
2184 arg += in[pos];
2185 pos++;
2186 break;
2187 case ')':
2188 case ']':
2189 case '}':
2190 delimDepth--;
2191 if (pos == startPos || delimDepth >= 0) {
2192 arg += in[pos];
2193 pos++;
2194 }
2195 break;
2196 case '\\':
2197 if (verbatim) {
2198 arg += in[pos];
2199 pos++;
2200 }
2201 else {
2202 pos++;
2203 if (pos < (int) in.length()) {
2204 if (in[pos].isLetterOrNumber())
2205 break;
2206 arg += in[pos];
2207 if (in[pos].isSpace()) {
2208 skipAllSpaces();
2209 }
2210 else {
2211 pos++;
2212 }
2213 }
2214 }
2215 break;
2216 default:
2217 arg += in[pos];
2218 pos++;
2219 }
2220 }
2221 if ((arg.length() > 1) &&
2222 (QString(".,:;!?").indexOf(in[pos - 1]) != -1) &&
2223 !arg.endsWith("...")) {
2224 arg.truncate(arg.length() - 1);
2225 pos--;
2226 }
2227 if (arg.length() > 2 && in.mid(pos - 2, 2) == "'s") {
2228 arg.truncate(arg.length() - 2);
2229 pos -= 2;
2230 }
2231 }
2232 return arg.simplified();
2233}
2234
2235QString DocParser::getOptionalArgument()
2236{
2237 skipSpacesOrOneEndl();
2238 if (pos + 1 < (int) in.length() && in[pos] == '\\' &&
2239 in[pos + 1].isLetterOrNumber()) {
2240 return "";
2241 }
2242 else {
2243 return getArgument();
2244 }
2245}
2246
2247QString DocParser::getRestOfLine()
2248{
2249 QString t;
2250
2251 skipSpacesOnLine();
2252
2253 bool trailingSlash = false;
2254
2255 do {
2256 int begin = pos;
2257
2258 while (pos < in.size() && in[pos] != '\n') {
2259 if (in[pos] == '\\' && !trailingSlash) {
2260 trailingSlash = true;
2261 ++pos;
2262 while ((pos < in.size()) &&
2263 in[pos].isSpace() &&
2264 (in[pos] != '\n'))
2265 ++pos;
2266 }
2267 else {
2268 trailingSlash = false;
2269 ++pos;
2270 }
2271 }
2272
2273 if (!t.isEmpty())
2274 t += " ";
2275 t += in.mid(begin, pos - begin).simplified();
2276
2277 if (trailingSlash) {
2278 t.chop(1);
2279 t = t.simplified();
2280 }
2281 if (pos < in.size())
2282 ++pos;
2283 } while (pos < in.size() && trailingSlash);
2284
2285 return t;
2286}
2287
2288/*!
2289 The metacommand argument is normally the remaining text to
2290 the right of the metacommand itself. The extra blanks are
2291 stripped and the argument string is returned.
2292 */
2293QString DocParser::getMetaCommandArgument(const QString &cmdStr)
2294{
2295 skipSpacesOnLine();
2296
2297 int begin = pos;
2298 int parenDepth = 0;
2299
2300 while (pos < in.size() && (in[pos] != '\n' || parenDepth > 0)) {
2301 if (in.at(pos) == '(')
2302 ++parenDepth;
2303 else if (in.at(pos) == ')')
2304 --parenDepth;
2305
2306 ++pos;
2307 }
2308 if (pos == in.size() && parenDepth > 0) {
2309 pos = begin;
2310 location().warning(tr("Unbalanced parentheses in '%1'").arg(cmdStr));
2311 }
2312
2313 QString t = in.mid(begin, pos - begin).simplified();
2314 skipSpacesOnLine();
2315 return t;
2316}
2317
2318QString DocParser::getUntilEnd(int cmd)
2319{
2320 int endCmd = endCmdFor(cmd);
2321 QRegExp rx("\\\\" + cmdName(endCmd) + "\\b");
2322 QString t;
2323 int end = rx.indexIn(in, pos);
2324
2325 if (end == -1) {
2326 location().warning(tr("Missing '\\%1'").arg(cmdName(endCmd)));
2327 pos = in.length();
2328 }
2329 else {
2330 t = in.mid(pos, end - pos);
2331 pos = end + rx.matchedLength();
2332 }
2333 return t;
2334}
2335
2336QString DocParser::getCode(int cmd, CodeMarker *marker)
2337{
2338 QString code = untabifyEtc(getUntilEnd(cmd));
2339 int indent = indentLevel(code);
2340 if (indent < minIndent)
2341 minIndent = indent;
2342 code = unindent(minIndent, code);
2343 marker = CodeMarker::markerForCode(code);
2344 return marker->markedUpCode(code, 0, "");
2345}
2346
2347/*!
2348 Used only for generating doxygen output.
2349 */
2350QString DocParser::getUnmarkedCode(int cmd)
2351{
2352 QString code = getUntilEnd(cmd);
2353#if 0
2354 int indent = indentLevel(code);
2355 if (indent < minIndent)
2356 minIndent = indent;
2357 code = unindent(minIndent, code);
2358#endif
2359 return code;
2360}
2361
2362bool DocParser::isBlankLine()
2363{
2364 int i = pos;
2365
2366 while (i < len && in[i].isSpace()) {
2367 if (in[i] == '\n')
2368 return true;
2369 i++;
2370 }
2371 return false;
2372}
2373
2374bool DocParser::isLeftBraceAhead()
2375{
2376 int numEndl = 0;
2377 int i = pos;
2378
2379 while (i < len && in[i].isSpace() && numEndl < 2) {
2380 // ### bug with '\\'
2381 if (in[i] == '\n')
2382 numEndl++;
2383 i++;
2384 }
2385 return numEndl < 2 && i < len && in[i] == '{';
2386}
2387
2388void DocParser::skipSpacesOnLine()
2389{
2390 while ((pos < in.length()) &&
2391 in[pos].isSpace() &&
2392 (in[pos].unicode() != '\n'))
2393 ++pos;
2394}
2395
2396void DocParser::skipSpacesOrOneEndl()
2397{
2398 int firstEndl = -1;
2399 while (pos < (int) in.length() && in[pos].isSpace()) {
2400 QChar ch = in[pos];
2401 if (ch == '\n') {
2402 if (firstEndl == -1) {
2403 firstEndl = pos;
2404 }
2405 else {
2406 pos = firstEndl;
2407 break;
2408 }
2409 }
2410 pos++;
2411 }
2412}
2413
2414void DocParser::skipAllSpaces()
2415{
2416 while (pos < len && in[pos].isSpace())
2417 pos++;
2418}
2419
2420void DocParser::skipToNextPreprocessorCommand()
2421{
2422 QRegExp rx("\\\\(?:" + cmdName(CMD_IF) + "|" +
2423 cmdName(CMD_ELSE) + "|" +
2424 cmdName(CMD_ENDIF) + ")\\b");
2425 int end = rx.indexIn(in, pos + 1); // ### + 1 necessary?
2426
2427 if (end == -1)
2428 pos = in.length();
2429 else
2430 pos = end;
2431}
2432
2433int DocParser::endCmdFor(int cmd)
2434{
2435 switch (cmd) {
2436 case CMD_ABSTRACT:
2437 return CMD_ENDABSTRACT;
2438 case CMD_BADCODE:
2439 return CMD_ENDCODE;
2440 case CMD_CHAPTER:
2441 return CMD_ENDCHAPTER;
2442 case CMD_CODE:
2443 return CMD_ENDCODE;
2444#ifdef QDOC_QML
2445 case CMD_QML:
2446 return CMD_ENDQML;
2447 case CMD_QMLTEXT:
2448 return CMD_ENDQMLTEXT;
2449#endif
2450 case CMD_FOOTNOTE:
2451 return CMD_ENDFOOTNOTE;
2452 case CMD_LEGALESE:
2453 return CMD_ENDLEGALESE;
2454 case CMD_LINK:
2455 return CMD_ENDLINK;
2456 case CMD_LIST:
2457 return CMD_ENDLIST;
2458 case CMD_NEWCODE:
2459 return CMD_ENDCODE;
2460 case CMD_OLDCODE:
2461 return CMD_NEWCODE;
2462 case CMD_OMIT:
2463 return CMD_ENDOMIT;
2464 case CMD_PART:
2465 return CMD_ENDPART;
2466 case CMD_QUOTATION:
2467 return CMD_ENDQUOTATION;
2468 case CMD_RAW:
2469 return CMD_ENDRAW;
2470 case CMD_SECTION1:
2471 return CMD_ENDSECTION1;
2472 case CMD_SECTION2:
2473 return CMD_ENDSECTION2;
2474 case CMD_SECTION3:
2475 return CMD_ENDSECTION3;
2476 case CMD_SECTION4:
2477 return CMD_ENDSECTION4;
2478 case CMD_SIDEBAR:
2479 return CMD_ENDSIDEBAR;
2480 case CMD_TABLE:
2481 return CMD_ENDTABLE;
2482 default:
2483 return cmd;
2484 }
2485}
2486
2487QString DocParser::cmdName(int cmd)
2488{
2489 return *cmds[cmd].alias;
2490}
2491
2492QString DocParser::endCmdName(int cmd)
2493{
2494 return cmdName(endCmdFor(cmd));
2495}
2496
2497QString DocParser::untabifyEtc(const QString& str)
2498{
2499 QString result;
2500 result.reserve(str.length());
2501 int column = 0;
2502
2503 for (int i = 0; i < str.length(); i++) {
2504 const QChar c = str.at(i);
2505 if (c == QLatin1Char('\r'))
2506 continue;
2507 if (c == QLatin1Char('\t')) {
2508 result += " " + (column % tabSize);
2509 column = ((column / tabSize) + 1) * tabSize;
2510 continue;
2511 }
2512 if (c == QLatin1Char('\n')) {
2513 while (result.endsWith(QLatin1Char(' ')))
2514 result.chop(1);
2515 result += c;
2516 column = 0;
2517 continue;
2518 }
2519 result += c;
2520 column++;
2521 }
2522
2523 while (result.endsWith("\n\n"))
2524 result.truncate(result.length() - 1);
2525 while (result.startsWith("\n"))
2526 result = result.mid(1);
2527
2528 return result;
2529}
2530
2531int DocParser::indentLevel(const QString& str)
2532{
2533 int minIndent = INT_MAX;
2534 int column = 0;
2535
2536 for (int i = 0; i < (int) str.length(); i++) {
2537 if (str[i] == '\n') {
2538 column = 0;
2539 }
2540 else {
2541 if (str[i] != ' ' && column < minIndent)
2542 minIndent = column;
2543 column++;
2544 }
2545 }
2546 return minIndent;
2547}
2548
2549QString DocParser::unindent(int level, const QString& str)
2550{
2551 if (level == 0)
2552 return str;
2553
2554 QString t;
2555 int column = 0;
2556
2557 for (int i = 0; i < (int) str.length(); i++) {
2558 if (str[i] == QLatin1Char('\n')) {
2559 t += '\n';
2560 column = 0;
2561 }
2562 else {
2563 if (column >= level)
2564 t += str[i];
2565 column++;
2566 }
2567 }
2568 return t;
2569}
2570
2571QString DocParser::slashed(const QString& str)
2572{
2573 QString result = str;
2574 result.replace("/", "\\/");
2575 return "/" + result + "/";
2576}
2577
2578#define COMMAND_BRIEF Doc::alias("brief")
2579
2580#ifdef QDOC_QML
2581#define COMMAND_QMLBRIEF Doc::alias("qmlbrief")
2582#endif
2583
2584#ifdef QDOC2DOX
2585#define DOXYGEN_INDENT 2
2586#define DOXYGEN_TAB_SIZE 4
2587#define DOXYGEN_INDENT_STRING " "
2588#define DOXYGEN_TAB_STRING " "
2589
2590static QRegExp ws_rx("\\s");
2591static QRegExp not_ws_rx("\\S");
2592
2593int DoxWriter::doxPass = 0;
2594QString DoxWriter::currentClass;
2595QSet<QString> DoxWriter::anchors;
2596QStringMap DoxWriter::exampleTitles;
2597QStringMap DoxWriter::headerFileTitles;
2598QStringMap DoxWriter::fileTitles;
2599QStringMap DoxWriter::groupTitles;
2600QStringMap DoxWriter::moduleTitles;
2601QStringMap DoxWriter::pageTitles;
2602QStringMap DoxWriter::externalPageTitles;
2603QStringMap DoxWriter::exampleTitlesInverse;
2604QStringMap DoxWriter::headerFileTitlesInverse;
2605QStringMap DoxWriter::fileTitlesInverse;
2606QStringMap DoxWriter::groupTitlesInverse;
2607QStringMap DoxWriter::moduleTitlesInverse;
2608QStringMap DoxWriter::pageTitlesInverse;
2609QStringMap DoxWriter::externalPageTitlesInverse;
2610QStringMultiMap DoxWriter::variables;
2611QStringMultiMap DoxWriter::properties;
2612QStringMultiMap DoxWriter::enums;
2613#endif
2614
2615Doc::Doc(const Location& start_loc,
2616 const Location& end_loc,
2617 const QString& source,
2618 const QSet<QString>& metaCommandSet)
2619{
2620 priv = new DocPrivate(start_loc,end_loc,source);
2621 DocParser parser;
2622 parser.parse(source,priv,metaCommandSet);
2623#ifdef QDOC2DOX
2624 if (DoxWriter::isDoxPass()) {
2625 DoxWriter doxWriter(source,priv);
2626 if (DoxWriter::isDoxPass(1))
2627 doxWriter.pass1();
2628 else
2629 doxWriter.pass2();
2630 }
2631#endif
2632}
2633
2634Doc::Doc(const Doc& doc)
2635 : priv(0)
2636{
2637 operator=(doc);
2638}
2639
2640Doc::~Doc()
2641{
2642 if (priv && priv->deref())
2643 delete priv;
2644}
2645
2646Doc &Doc::operator=(const Doc& doc)
2647{
2648 if (doc.priv)
2649 doc.priv->ref();
2650 if (priv && priv->deref())
2651 delete priv;
2652 priv = doc.priv;
2653 return *this;
2654}
2655
2656void Doc::renameParameters(const QStringList &oldNames,
2657 const QStringList &newNames)
2658{
2659 if (priv && oldNames != newNames) {
2660 detach();
2661
2662 priv->params = newNames.toSet();
2663
2664 Atom *atom = priv->text.firstAtom();
2665 while (atom) {
2666 if (atom->type() == Atom::FormattingLeft
2667 && atom->string() == ATOM_FORMATTING_PARAMETER) {
2668 atom = atom->next();
2669 if (!atom)
2670 return;
2671 int index = oldNames.indexOf(atom->string());
2672 if (index != -1 && index < newNames.count())
2673 atom->setString(newNames.at(index));
2674 }
2675 atom = atom->next();
2676 }
2677 }
2678}
2679
2680void Doc::simplifyEnumDoc()
2681{
2682 if (priv) {
2683 if (priv->isEnumDocSimplifiable()) {
2684 detach();
2685
2686 Text newText;
2687
2688 Atom *atom = priv->text.firstAtom();
2689 while (atom) {
2690 if ((atom->type() == Atom::ListLeft) &&
2691 (atom->string() == ATOM_LIST_VALUE)) {
2692 while (atom && ((atom->type() != Atom::ListRight) ||
2693 (atom->string() != ATOM_LIST_VALUE)))
2694 atom = atom->next();
2695 if (atom)
2696 atom = atom->next();
2697 }
2698 else {
2699 newText << *atom;
2700 atom = atom->next();
2701 }
2702 }
2703 priv->text = newText;
2704 }
2705 }
2706}
2707
2708void Doc::setBody(const Text &text)
2709{
2710 detach();
2711 priv->text = text;
2712}
2713
2714/*!
2715 Returns the starting location of a qdoc comment.
2716 */
2717const Location &Doc::location() const
2718{
2719 static const Location dummy;
2720 return priv == 0 ? dummy : priv->start_loc;
2721}
2722
2723const QString &Doc::source() const
2724{
2725 static QString null;
2726 return priv == 0 ? null : priv->src;
2727}
2728
2729bool Doc::isEmpty() const
2730{
2731 return priv == 0 || priv->src.isEmpty();
2732}
2733
2734const Text& Doc::body() const
2735{
2736 static const Text dummy;
2737 return priv == 0 ? dummy : priv->text;
2738}
2739
2740Text Doc::briefText() const
2741{
2742 return body().subText(Atom::BriefLeft, Atom::BriefRight);
2743}
2744
2745Text Doc::trimmedBriefText(const QString &className) const
2746{
2747 QString classNameOnly = className;
2748 if (className.contains("::"))
2749 classNameOnly = className.split("::").last();
2750
2751 Text originalText = briefText();
2752 Text resultText;
2753 const Atom *atom = originalText.firstAtom();
2754 if (atom) {
2755 QString briefStr;
2756 QString whats;
2757 bool standardWording = true;
2758
2759 /*
2760 This code is really ugly. The entire \brief business
2761 should be rethought.
2762 */
2763 while (atom && (atom->type() == Atom::AutoLink || atom->type() == Atom::String)) {
2764 briefStr += atom->string();
2765 atom = atom->next();
2766 }
2767
2768 QStringList w = briefStr.split(" ");
2769 if (!w.isEmpty() && w.first() == "The")
2770 w.removeFirst();
2771 else {
2772 location().warning(
2773 tr("Nonstandard wording in '\\%1' text for '%2' (expected 'The')")
2774 .arg(COMMAND_BRIEF).arg(className));
2775 standardWording = false;
2776 }
2777
2778 if (!w.isEmpty() && (w.first() == className || w.first() == classNameOnly))
2779 w.removeFirst();
2780 else {
2781 location().warning(
2782 tr("Nonstandard wording in '\\%1' text for '%2' (expected '%3')")
2783 .arg(COMMAND_BRIEF).arg(className).arg(className));
2784 standardWording = false;
2785 }
2786
2787 if (!w.isEmpty() && (w.first() == "class" || w.first() == "widget"
2788 || w.first() == "namespace" || w.first() == "header"))
2789 w.removeFirst();
2790 else {
2791 location().warning(
2792 tr("Nonstandard wording in '\\%1' text for '%2' ("
2793 "expected 'class', 'widget', 'namespace' or 'header')")
2794 .arg(COMMAND_BRIEF).arg(className));
2795 standardWording = false;
2796 }
2797
2798 if (!w.isEmpty() && (w.first() == "is" || w.first() == "provides"))
2799 w.removeFirst();
2800
2801 if (!w.isEmpty() && (w.first() == "a" || w.first() == "an"))
2802 w.removeFirst();
2803
2804 whats = w.join(" ");
2805 if (whats.endsWith("."))
2806 whats.truncate(whats.length() - 1);
2807
2808 if (whats.isEmpty()) {
2809 location().warning(
2810 tr("Nonstandard wording in '\\%1' text for '%2' (expected more text)")
2811 .arg(COMMAND_BRIEF).arg(className));
2812 standardWording = false;
2813 }
2814 else
2815 whats[0] = whats[0].toUpper();
2816
2817 // ### move this once \brief is abolished for properties
2818 if (standardWording)
2819 resultText << whats;
2820 }
2821 return resultText;
2822}
2823
2824Text Doc::legaleseText() const
2825{
2826 if (priv == 0 || !priv->hasLegalese)
2827 return Text();
2828 else
2829 return body().subText(Atom::LegaleseLeft, Atom::LegaleseRight);
2830}
2831
2832const QString& Doc::baseName() const
2833{
2834 static QString null;
2835 if (priv == 0 || priv->extra == 0) {
2836 return null;
2837 }
2838 else {
2839 return priv->extra->baseName;
2840 }
2841}
2842
2843Doc::SectioningUnit Doc::granularity() const
2844{
2845 if (priv == 0 || priv->extra == 0) {
2846 return DocPrivateExtra().granularity;
2847 }
2848 else {
2849 return priv->extra->granularity;
2850 }
2851}
2852
2853#if notyet // ###
2854Doc::SectioningUnit Doc::sectioningUnit() const
2855{
2856 if (priv == 0 || priv->extra == 0) {
2857 return DocPrivateExtra().sectioningUnit;
2858 }
2859 else {
2860 return priv->extra->sectioningUnit;
2861 }
2862}
2863#endif
2864
2865const QSet<QString> &Doc::parameterNames() const
2866{
2867 return priv == 0 ? *null_Set_QString() : priv->params;
2868}
2869
2870const QStringList &Doc::enumItemNames() const
2871{
2872 return priv == 0 ? *null_QStringList() : priv->enumItemList;
2873}
2874
2875const QStringList &Doc::omitEnumItemNames() const
2876{
2877 return priv == 0 ? *null_QStringList() : priv->omitEnumItemList;
2878}
2879
2880const QSet<QString> &Doc::metaCommandsUsed() const
2881{
2882 return priv == 0 ? *null_Set_QString() : priv->metacommandsUsed;
2883}
2884
2885QStringList Doc::metaCommandArgs(const QString& metacommand) const
2886{
2887 return priv == 0 ? QStringList() : priv->metaCommandMap.value(metacommand);
2888}
2889
2890const QList<Text> &Doc::alsoList() const
2891{
2892 return priv == 0 ? *null_QList_Text() : priv->alsoList;
2893}
2894
2895bool Doc::hasTableOfContents() const
2896{
2897 return priv && priv->extra && !priv->extra->tableOfContents.isEmpty();
2898}
2899
2900bool Doc::hasKeywords() const
2901{
2902 return priv && priv->extra && !priv->extra->keywords.isEmpty();
2903}
2904
2905bool Doc::hasTargets() const
2906{
2907 return priv && priv->extra && !priv->extra->targets.isEmpty();
2908}
2909
2910const QList<Atom *> &Doc::tableOfContents() const
2911{
2912 priv->constructExtra();
2913 return priv->extra->tableOfContents;
2914}
2915
2916const QList<int> &Doc::tableOfContentsLevels() const
2917{
2918 priv->constructExtra();
2919 return priv->extra->tableOfContentsLevels;
2920}
2921
2922const QList<Atom *> &Doc::keywords() const
2923{
2924 priv->constructExtra();
2925 return priv->extra->keywords;
2926}
2927
2928const QList<Atom *> &Doc::targets() const
2929{
2930 priv->constructExtra();
2931 return priv->extra->targets;
2932}
2933
2934const QStringMap &Doc::metaTagMap() const
2935{
2936 return priv && priv->extra ? priv->extra->metaMap : *null_QStringMap();
2937}
2938
2939void Doc::initialize(const Config& config)
2940{
2941 DocParser::tabSize = config.getInt(CONFIG_TABSIZE);
2942 DocParser::exampleFiles = config.getStringList(CONFIG_EXAMPLES);
2943 DocParser::exampleDirs = config.getStringList(CONFIG_EXAMPLEDIRS);
2944 DocParser::sourceFiles = config.getStringList(CONFIG_SOURCES);
2945 DocParser::sourceDirs = config.getStringList(CONFIG_SOURCEDIRS);
2946 DocParser::quoting = config.getBool(CONFIG_QUOTINGINFORMATION);
2947
2948 QStringMap reverseAliasMap;
2949
2950 QSet<QString> commands = config.subVars(CONFIG_ALIAS);
2951 QSet<QString>::ConstIterator c = commands.begin();
2952 while (c != commands.end()) {
2953 QString alias = config.getString(CONFIG_ALIAS + Config::dot + *c);
2954 if (reverseAliasMap.contains(alias)) {
2955 config.lastLocation().warning(tr("Command name '\\%1' cannot stand"
2956 " for both '\\%2' and '\\%3'")
2957 .arg(alias)
2958 .arg(reverseAliasMap[alias])
2959 .arg(*c));
2960 }
2961 else {
2962 reverseAliasMap.insert(alias, *c);
2963 }
2964 aliasMap()->insert(*c, alias);
2965 ++c;
2966 }
2967
2968 int i = 0;
2969 while (cmds[i].english) {
2970 cmds[i].alias = new QString(alias(cmds[i].english));
2971 cmdHash()->insert(*cmds[i].alias, cmds[i].no);
2972
2973 if (cmds[i].no != i)
2974 Location::internalError(tr("command %1 missing").arg(i));
2975 i++;
2976 }
2977
2978 QSet<QString> macroNames = config.subVars(CONFIG_MACRO);
2979 QSet<QString>::ConstIterator n = macroNames.begin();
2980 while (n != macroNames.end()) {
2981 QString macroDotName = CONFIG_MACRO + Config::dot + *n;
2982 Macro macro;
2983 macro.numParams = -1;
2984 macro.defaultDef = config.getString(macroDotName);
2985 if (!macro.defaultDef.isEmpty()) {
2986 macro.defaultDefLocation = config.lastLocation();
2987 macro.numParams = Config::numParams(macro.defaultDef);
2988 }
2989 bool silent = false;
2990
2991 QSet<QString> formats = config.subVars(macroDotName);
2992 QSet<QString>::ConstIterator f = formats.begin();
2993 while (f != formats.end()) {
2994 QString def = config.getString(macroDotName + Config::dot + *f);
2995 if (!def.isEmpty()) {
2996 macro.otherDefs.insert(*f, def);
2997 int m = Config::numParams(macro.defaultDef);
2998 if (macro.numParams == -1) {
2999 macro.numParams = m;
3000 }
3001 else if (macro.numParams != m) {
3002 if (!silent) {
3003 QString other = tr("default");
3004 if (macro.defaultDef.isEmpty())
3005 other = macro.otherDefs.begin().key();
3006 config.lastLocation().warning(tr("Macro '\\%1' takes"
3007 " inconsistent number"
3008 " of arguments (%2"
3009 " %3, %4 %5)")
3010 .arg(*n)
3011 .arg(*f)
3012 .arg(m)
3013 .arg(other)
3014 .arg(macro.numParams));
3015 silent = true;
3016 }
3017 if (macro.numParams < m)
3018 macro.numParams = m;
3019 }
3020 }
3021 ++f;
3022 }
3023
3024 if (macro.numParams != -1)
3025 macroHash()->insert(*n, macro);
3026 ++n;
3027 }
3028}
3029
3030void Doc::terminate()
3031{
3032 DocParser::exampleFiles.clear();
3033 DocParser::exampleDirs.clear();
3034 DocParser::sourceFiles.clear();
3035 DocParser::sourceDirs.clear();
3036 aliasMap()->clear();
3037 cmdHash()->clear();
3038 macroHash()->clear();
3039
3040 int i = 0;
3041 while (cmds[i].english) {
3042 delete cmds[i].alias;
3043 cmds[i].alias = 0;
3044 ++i;
3045 }
3046}
3047
3048QString Doc::alias(const QString &english)
3049{
3050 return aliasMap()->value(english, english);
3051}
3052
3053/*!
3054 Trims the deadwood out of \a str. i.e., this function
3055 cleans up \a str.
3056 */
3057void Doc::trimCStyleComment(Location& location, QString& str)
3058{
3059 QString cleaned;
3060 Location m = location;
3061 bool metAsterColumn = true;
3062 int asterColumn = location.columnNo() + 1;
3063 int i;
3064
3065 for (i = 0; i < (int) str.length(); i++) {
3066 if (m.columnNo() == asterColumn) {
3067 if (str[i] != '*')
3068 break;
3069 cleaned += ' ';
3070 metAsterColumn = true;
3071 }
3072 else {
3073 if (str[i] == '\n') {
3074 if (!metAsterColumn)
3075 break;
3076 metAsterColumn = false;
3077 }
3078 cleaned += str[i];
3079 }
3080 m.advance(str[i]);
3081 }
3082 if (cleaned.length() == str.length())
3083 str = cleaned;
3084
3085 for (int i = 0; i < 3; i++)
3086 location.advance(str[i]);
3087 str = str.mid(3, str.length() - 5);
3088}
3089
3090CodeMarker *Doc::quoteFromFile(const Location &location,
3091 Quoter &quoter,
3092 const QString &fileName)
3093{
3094 quoter.reset();
3095
3096 QString code;
3097
3098 QString userFriendlyFilePath;
3099 QString filePath = Config::findFile(location,
3100 DocParser::exampleFiles,
3101 DocParser::exampleDirs,
3102 fileName, userFriendlyFilePath);
3103 if (filePath.isEmpty()) {
3104 location.warning(tr("Cannot find example file '%1'").arg(fileName));
3105 }
3106 else {
3107 QFile inFile(filePath);
3108 if (!inFile.open(QFile::ReadOnly)) {
3109 location.warning(tr("Cannot open example file '%1'").arg(userFriendlyFilePath));
3110 }
3111 else {
3112 QTextStream inStream(&inFile);
3113 code = DocParser::untabifyEtc(inStream.readAll());
3114 }
3115 }
3116
3117 QString dirPath = QFileInfo(filePath).path();
3118 CodeMarker *marker = CodeMarker::markerForFileName(fileName);
3119 quoter.quoteFromFile(userFriendlyFilePath,
3120 code,
3121 marker->markedUpCode(code, 0, dirPath));
3122 return marker;
3123}
3124
3125QString Doc::canonicalTitle(const QString &title)
3126{
3127 // The code below is equivalent to the following chunk, but _much_
3128 // faster (accounts for ~10% of total running time)
3129 //
3130 // QRegExp attributeExpr("[^A-Za-z0-9]+");
3131 // QString result = title.toLower();
3132 // result.replace(attributeExpr, " ");
3133 // result = result.simplified();
3134 // result.replace(QLatin1Char(' '), QLatin1Char('-'));
3135
3136 QString result;
3137 result.reserve(title.size());
3138
3139 bool slurping = false;
3140 bool begun = false;
3141 int lastAlnum = 0;
3142 for (int i = 0; i != title.size(); ++i) {
3143 uint c = title.at(i).unicode();
3144 if (c >= 'A' && c <= 'Z')
3145 c -= 'A' - 'a';
3146 bool alnum = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
3147 if (alnum) {
3148 result += QLatin1Char(c);
3149 begun = true;
3150 slurping = false;
3151 lastAlnum = result.size();
3152 }
3153 else if (!slurping) {
3154 if (begun)
3155 result += QLatin1Char('-');
3156 slurping = true;
3157 }
3158 else {
3159 // !alnum && slurping -> nothin
3160 }
3161 }
3162 result.truncate(lastAlnum);
3163 return result;
3164}
3165
3166void Doc::detach()
3167{
3168 if (!priv) {
3169 priv = new DocPrivate;
3170 return;
3171 }
3172 if (priv->count == 1)
3173 return;
3174
3175 --priv->count;
3176
3177 DocPrivate *newPriv = new DocPrivate(*priv);
3178 newPriv->count = 1;
3179 if (priv->extra)
3180 newPriv->extra = new DocPrivateExtra(*priv->extra);
3181
3182 priv = newPriv;
3183}
3184
3185#ifdef QDOC2DOX
3186/*!
3187 Sets the doxygen writer pass to \a pass. You can use
3188 isDoxPass(), with or without a parameter, to test if
3189 you are in a doxygen writer run or in a specific pass
3190 of a doxygen writer run.
3191
3192 This function is only called from main() if either the
3193 \e doxygen1 or \e doxygen2 flag is passed to qdoc3 on
3194 the command line.
3195 */
3196void DoxWriter::setDoxPass(int pass)
3197{
3198 qDebug() << "SETTING doxygen pass to " << pass
3199 << " in DoxWriter::setDoxPass()";
3200 doxPass = pass;
3201}
3202
3203/*!
3204 Returns true if the doxygen pass is set to \a pass,
3205 which means we are in the specified \a pass of a doxygen
3206 writer run of qdoc3.
3207 */
3208bool DoxWriter::isDoxPass(int pass) { return (doxPass == pass); }
3209
3210/*!
3211 Returns true if the doxygen pass is 1 or 2, which
3212 means this is a doxygen writer run to transform qdoc
3213 comments into doxygen comments.
3214 */
3215bool DoxWriter::isDoxPass() { return (doxPass > 0); }
3216
3217bool DoxWriter::conversionRequired() const
3218{
3219 /*
3220 Loop through all the topic commands searching for
3221 one that must be transformed to doxygen format. If
3222 one is found, return true.
3223 */
3224 QCommandMap::const_iterator i;
3225 i = priv->metaCommandMap.constBegin();
3226 while (i != priv->metaCommandMap.constEnd()) {
3227 QString s = i.key();
3228 if (s == "enum")
3229 return true;
3230 else if (s == "example")
3231 return true;
3232 else if (s == "externalpage")
3233 return true;
3234 else if (s == "group")
3235 return true;
3236 else if (s == "headerfile")
3237 return true;
3238 else if (s == "module")
3239 return true;
3240 else if (s == "page")
3241 return true;
3242 else if (s == "property")
3243 return true;
3244 else if (s == "typedef")
3245 return true;
3246 else if (s == "variable")
3247 return true;
3248 else if (s == "overload")
3249 return true;
3250 else if (s == "reimp")
3251 return true;
3252 else if (s == "relates")
3253 return true;
3254 else if (s == "macro")
3255 return true;
3256 else {
3257#if 0
3258 if (s == "class")
3259 else if (s == "namespace")
3260 else if (s == "service")
3261 else if (s == "inheaderfile")
3262 else if (s == "file")
3263 else if (s == "fn")
3264 else if (s == "contentspage")
3265 else if (s == "nextpage")
3266 else if (s == "previous")
3267 else if (s == "indexpage")
3268 else if (s == "startpage")
3269#endif
3270 }
3271 ++i;
3272 }
3273
3274 /*
3275 Loop through all the qdoc atoms searching for one
3276 that must be transformed to doxygen format. If one
3277 is found, return true.
3278 */
3279 const Atom* next = priv->text.firstAtom();
3280 while (next != 0) {
3281 Atom::Type atomType = next->type();
3282 switch (atomType) {
3283 case Atom::C:
3284 case Atom::CaptionLeft:
3285 case Atom::Code:
3286 case Atom::CodeBad:
3287 case Atom::CodeNew:
3288 case Atom::CodeOld:
3289 case Atom::CodeQuoteArgument:
3290 case Atom::CodeQuoteCommand:
3291 case Atom::FootnoteLeft:
3292 case Atom::FormatElse:
3293 case Atom::FormatEndif:
3294 case Atom::FormatIf:
3295 case Atom::GeneratedList:
3296 case Atom::Image:
3297 case Atom::ImageText:
3298 case Atom::InlineImage:
3299 case Atom::LegaleseLeft:
3300 case Atom::LineBreak:
3301 case Atom::Link:
3302 case Atom::LinkNode:
3303 case Atom::ListLeft:
3304 case Atom::ListItemNumber:
3305 case Atom::ListTagLeft:
3306 case Atom::ListItemLeft:
3307 case Atom::QuotationLeft:
3308 case Atom::RawString:
3309 case Atom::SectionLeft:
3310 case Atom::SectionHeadingLeft:
3311 case Atom::SidebarLeft:
3312 case Atom::SnippetCommand:
3313 case Atom::SnippetIdentifier:
3314 case Atom::SnippetLocation:
3315 case Atom::TableLeft:
3316 case Atom::TableHeaderLeft:
3317 case Atom::TableRowLeft:
3318 case Atom::TableItemLeft:
3319 case Atom::TableOfContents:
3320 case Atom::Target:
3321 return true;
3322 case Atom::AbstractLeft:
3323 case Atom::AbstractRight:
3324 case Atom::AutoLink:
3325 case Atom::BaseName:
3326 case Atom::BriefLeft:
3327 case Atom::BriefRight:
3328 case Atom::CaptionRight:
3329 case Atom::FormattingLeft:
3330 case Atom::FormattingRight:
3331 case Atom::Nop:
3332 case Atom::ParaLeft:
3333 case Atom::ParaRight:
3334 case Atom::FootnoteRight:
3335 case Atom::LegaleseRight:
3336 case Atom::ListTagRight:
3337 case Atom::ListItemRight:
3338 case Atom::ListRight:
3339 case Atom::QuotationRight:
3340 case Atom::SectionRight:
3341 case Atom::SectionHeadingRight:
3342 case Atom::SidebarRight:
3343 case Atom::String:
3344 case Atom::TableRight:
3345 case Atom::TableHeaderRight:
3346 case Atom::TableRowRight:
3347 case Atom::TableItemRight:
3348 default:
3349 break;
3350 }
3351 next = next->next();
3352 }
3353 return false;
3354}
3355
3356/*!
3357 A convenience function to write a qdoc metacommand as a
3358 doxygen command, without conversion. i.e., some of the
3359 qdoc metacommands don't require conversion for doxygen.
3360 */
3361void DoxWriter::writeCommand(QCommandMap::const_iterator cmd)
3362{
3363 concatenate("\\" + cmd.key() + " " + cmd.value()[0]);
3364 newLine();
3365}
3366
3367/*!
3368 Convert the qdoc commands in the metacommand map to
3369 doxygen format. This function is called only in pass2().
3370 The metacommand map contains all the metacommands that
3371 were found in the qdoc comment that is being converted.
3372 The metacommands are the ones that begin with the '\'.
3373 These are not considered part of the text of the comment.
3374 The text is converted by convertText().
3375 */
3376void DoxWriter::convertMetaCommands()
3377{
3378 QCommandMap& metaCmdMap = priv->metaCommandMap;
3379 QCommandMap::iterator cmd;
3380 int c;
3381
3382 currentPage.clear();
3383 currentFn.clear();
3384 currentTitle.clear();
3385 currentEnum.clear();
3386 currentProperty.clear();
3387 currentVariable.clear();
3388 currentClass.clear();
3389 currentExample.clear();
3390 currentGroup.clear();
3391 currentModule.clear();
3392 currentMacro.clear();
3393 currentService.clear();
3394 currentTypedef.clear();
3395 currentHeaderFile.clear();
3396 commentType = OtherComment;
3397
3398 if ((cmd = metaCmdMap.find("class")) != metaCmdMap.end()) {
3399 currentClass = cmd.value()[0];
3400 if ((c = currentClass.indexOf(' ')) > 0)
3401 currentClass = currentClass.left(c);
3402 writeCommand(cmd);
3403 metaCmdMap.erase(cmd);
3404 commentType = ClassComment;
3405 }
3406 else if ((cmd = metaCmdMap.find("fn")) != metaCmdMap.end()) {
3407 currentFn = cmd.value()[0];
3408 writeCommand(cmd);
3409 metaCmdMap.erase(cmd);
3410 commentType = FnComment;
3411 }
3412 else if ((cmd = metaCmdMap.find("enum")) != metaCmdMap.end()) {
3413 currentEnum = cmd.value()[0];
3414 if ((c = currentEnum.lastIndexOf("::")) > 0) {
3415 currentClass = currentEnum.left(c);
3416 currentEnum = currentEnum.right(currentEnum.size()-c-2);
3417 qDebug() << "currentEnum =" << currentEnum;
3418 qDebug() << "currentClass =" << currentClass;
3419 }
3420 writeCommand(cmd);
3421 metaCmdMap.erase(cmd);
3422 commentType = EnumComment;
3423 }
3424 else if ((cmd = metaCmdMap.find("property")) != metaCmdMap.end()) {
3425 currentClass = cmd.value()[0];
3426 if ((c = currentClass.lastIndexOf("::")) > 0) {
3427 currentProperty = currentClass.right(currentClass.size()-c-2);
3428 currentClass = currentClass.left(c);
3429 qDebug() << "currentProperty =" << currentProperty;
3430 qDebug() << "currentClass =" << currentClass;
3431 }
3432 writeCommand(cmd);
3433 metaCmdMap.erase(cmd);
3434 commentType = PropertyComment;
3435 }
3436 else if ((cmd = metaCmdMap.find("variable")) != metaCmdMap.end()) {
3437 currentClass = cmd.value()[0];
3438 if ((c = currentClass.lastIndexOf("::")) > 0) {
3439 currentVariable = currentClass.right(currentClass.size()-c-2);
3440 currentClass = currentClass.left(c);
3441 qDebug() << "currentVariable =" << currentVariable;
3442 qDebug() << "currentClass =" << currentClass;
3443 }
3444 concatenate("\\var " + cmd.value()[0]);
3445 newLine();
3446 metaCmdMap.erase(cmd);
3447 commentType = VariableComment;
3448 }
3449
3450 if ((cmd = metaCmdMap.find("page")) != metaCmdMap.end()) {
3451 currentPage = cmd.value()[0];
3452 QString htmlFile = currentPage;
3453 const QString* title = getPageTitle(htmlFile);
3454 QStringList parts = htmlFile.split('.');
3455 metaCmdMap.erase(cmd);
3456 if (title) {
3457 concatenate("\\page " + parts[0] + " " + *title);
3458 newLine();
3459 }
3460 commentType = PageComment;
3461 qDebug() << "currentPage =" << currentPage;
3462 }
3463
3464 if ((cmd = metaCmdMap.find("example")) != metaCmdMap.end()) {
3465 currentExample = cmd.value()[0];
3466 metaCmdMap.erase(cmd);
3467 commentType = ExampleComment;
3468 qDebug() << "currentExample =" << currentExample;
3469 }
3470
3471 if ((cmd = metaCmdMap.find("macro")) != metaCmdMap.end()) {
3472 currentMacro = cmd.value()[0];
3473 metaCmdMap.erase(cmd);
3474 commentType = MacroComment;
3475 qDebug() << "currentMacro =" << currentMacro;
3476 }
3477
3478 if ((cmd = metaCmdMap.find("group")) != metaCmdMap.end()) {
3479 currentGroup = cmd.value()[0];
3480 metaCmdMap.erase(cmd);
3481 commentType = GroupComment;
3482 qDebug() << "currentGroup =" << currentGroup;
3483 }
3484
3485 if ((cmd = metaCmdMap.find("module")) != metaCmdMap.end()) {
3486 currentModule = cmd.value()[0];
3487 metaCmdMap.erase(cmd);
3488 commentType = ModuleComment;
3489 qDebug() << "currentModule =" << currentModule;
3490 }
3491
3492 if ((cmd = metaCmdMap.find("headerfile")) != metaCmdMap.end()) {
3493 currentHeaderFile = cmd.value()[0];
3494 metaCmdMap.erase(cmd);
3495 commentType = HeaderFileComment;
3496 qDebug() << "currentHeaderFile =" << currentHeaderFile;
3497 }
3498
3499 if ((cmd = metaCmdMap.find("typedef")) != metaCmdMap.end()) {
3500 currentClass = cmd.value()[0];
3501 if ((c = currentClass.lastIndexOf("::")) > 0) {
3502 currentTypedef = currentClass.right(currentClass.size()-c-2);
3503 currentClass = currentClass.left(c);
3504 }
3505 metaCmdMap.erase(cmd);
3506 commentType = TypedefComment;
3507 qDebug() << "currentTypedef =" << currentTypedef;
3508 qDebug() << "currentClass =" << currentClass;
3509 }
3510
3511 cmd = priv->metaCommandMap.begin();
3512 while (cmd != priv->metaCommandMap.end()) {
3513 for (int i=0; i<cmd.value().size(); i++) {
3514 concatenate("\\" + cmd.key() + " " + cmd.value()[i]);
3515 newLine();
3516 }
3517 //qDebug() << " " << cmd.key() << ": " << cmd.value();
3518 ++cmd;
3519 }
3520}
3521
3522/*!
3523 Convert the qdoc text to doxygen format. The metacommands
3524 are converted by convertMetaCommands(). This function is
3525 called in pass2().
3526 */
3527void DoxWriter::convertText()
3528{
3529 const Atom* prev = 0;
3530 const Atom* next = priv->text.firstAtom();
3531 while (next != 0) {
3532 next->dump();
3533 Atom::Type atomType = next->type();
3534 switch (atomType) {
3535 case Atom::AbstractLeft:
3536 break;
3537 case Atom::AbstractRight:
3538 break;
3539 case Atom::AutoLink:
3540 concatenate(next->string());
3541 break;
3542 case Atom::BaseName:
3543 break;
3544 case Atom::BriefLeft:
3545 concatenate("\\brief ");
3546 break;
3547 case Atom::BriefRight:
3548 newLine();
3549 break;
3550 case Atom::C:
3551 tt(next);
3552 break;
3553 case Atom::CaptionLeft:
3554 unhandled(next);
3555 break;
3556 case Atom::CaptionRight:
3557 unhandled(next);
3558 break;
3559 case Atom::Code:
3560 code(next);
3561 break;
3562 case Atom::CodeBad:
3563 code(next);
3564 break;
3565 case Atom::CodeNew:
3566 newLine();
3567 concatenate("you can rewrite it as");
3568 code(next);
3569 break;
3570 case Atom::CodeOld:
3571 newLine();
3572 concatenate("For example, if you have code like");
3573 code(next);
3574 break;
3575 case Atom::CodeQuoteArgument:
3576 unhandled(next);
3577 break;
3578 case Atom::CodeQuoteCommand:
3579 next = codeQuoteCommand(next);
3580 break;
3581 case Atom::FootnoteLeft:
3582 break;
3583 case Atom::FootnoteRight:
3584 break;
3585 case Atom::FormatElse:
3586 formatElse();
3587 break;
3588 case Atom::FormatEndif:
3589 formatEndif();
3590 break;
3591 case Atom::FormatIf:
3592 formatIf(next);
3593 break;
3594 case Atom::FormattingLeft:
3595 formattingLeft(next,next->next());
3596 break;
3597 case Atom::FormattingRight:
3598 formattingRight(next,prev);
3599 break;
3600 case Atom::GeneratedList:
3601 break;
3602 case Atom::Image:
3603 break;
3604 case Atom::ImageText:
3605 break;
3606 case Atom::InlineImage:
3607 break;
3608 case Atom::LegaleseLeft:
3609 break;
3610 case Atom::LegaleseRight:
3611 break;
3612 case Atom::LineBreak:
3613 break;
3614 case Atom::Link:
3615 next = link(next);
3616 break;
3617 case Atom::LinkNode:
3618 break;
3619 case Atom::ListLeft:
3620 {
3621 bool nested = false;
3622 if (structs.isEmpty()) {
3623 const Atom* i = next->next();
3624 while (i->type() != Atom::ListRight) {
3625 if ((i->type() == Atom::ListLeft) ||
3626 (i->type() == Atom::TableLeft)) {
3627 nested = true;
3628 break;
3629 }
3630 i = i->next();
3631 }
3632 }
3633 else
3634 nested = true;
3635 StructDesc d(BulletList,nested);
3636 if (next->string() == "numeric")
3637 d.structType = NumericList;
3638 else if (next->string() == "value") {
3639 d.structType = ValueList;
3640 }
3641 else if (next->string() != "bullet")
3642 qDebug() << "UNKNOWN LIST TYPE" << next->string();
3643 structs.push(d);
3644 if (nested || (d.structType != BulletList)) {
3645 if (d.structType == BulletList)
3646 concatenate("<ul>");
3647 else if (d.structType == NumericList)
3648 concatenate("<ol>");
3649 else if (d.structType == ValueList)
3650 concatenate("<dl>");
3651 newLine();
3652 }
3653 }
3654 break;
3655 case Atom::ListItemNumber:
3656 structs.top().count = next->string().toInt();
3657 break;
3658 case Atom::ListTagLeft:
3659 {
3660 structs.top().count++;
3661 concatenate("<dt>");
3662 const Atom* n = next->next();
3663 if (n->type() == Atom::String) {
3664 qDebug() << "ENUM VALUE" << n->string();
3665 }
3666 else
3667 qDebug() << "NOT EN ENUM";
3668 }
3669 break;
3670 case Atom::ListTagRight:
3671 concatenate("</dt>");
3672 break;
3673 case Atom::ListItemLeft:
3674 {
3675 newLine();
3676 const StructDesc& d = structs.top();
3677 if (d.structType == BulletList) {
3678 if (!d.nested)
3679 concatenate("\\arg ");
3680 else
3681 concatenate("<li>");
3682 }
3683 else if (d.structType == NumericList)
3684 concatenate("<li>");
3685 else if (d.structType == ValueList)
3686 concatenate("<dd>");
3687 }
3688 break;
3689 case Atom::ListItemRight:
3690 {
3691 const StructDesc& d = structs.top();
3692 if (d.structType == BulletList) {
3693 if (d.nested) {
3694 concatenate("</li>");
3695 newLine();
3696 }
3697 }
3698 else if (d.structType == NumericList) {
3699 concatenate("</li>");
3700 newLine();
3701 }
3702 else if (d.structType == ValueList) {
3703 concatenate("</dd>");
3704 newLine();
3705 }
3706 }
3707 break;
3708 case Atom::ListRight:
3709 {
3710 if (!structs.isEmpty()) {
3711 const StructDesc& d = structs.top();
3712 if (d.nested || (d.structType != BulletList)) {
3713 if (d.structType == BulletList)
3714 concatenate("</ul>");
3715 else if (d.structType == NumericList)
3716 concatenate("</ol>");
3717 else if (d.structType == ValueList)
3718 concatenate("</dl>");
3719 newLine();
3720 }
3721 structs.pop();
3722 }
3723 }
3724 break;
3725 case Atom::Nop:
3726 // nothing.
3727 break;
3728 case Atom::ParaLeft:
3729 if (structs.isEmpty())
3730 newLine();
3731 break;
3732 case Atom::ParaRight:
3733 {
3734 if (structs.isEmpty())
3735 newLine();
3736 else {
3737 const StructDesc& d = structs.top();
3738 if (d.nested || (d.structType != BulletList)) {
3739 Atom::Type t = next->next()->type();
3740 if ((t != Atom::ListItemRight) &&
3741 (t != Atom::TableItemRight))
3742 newLine();
3743 }
3744 else
3745 newLine();
3746 }
3747 }
3748 break;
3749 case Atom::QuotationLeft:
3750 break;
3751 case Atom::QuotationRight:
3752 break;
3753 case Atom::RawString:
3754 concatenate(next->string());
3755 break;
3756 case Atom::SectionLeft:
3757 // nothing.
3758 break;
3759 case Atom::SectionRight:
3760 // nothing.
3761 break;
3762 case Atom::SectionHeadingLeft:
3763 next = sectionHeading(next);
3764 break;
3765 case Atom::SectionHeadingRight:
3766 newLine();
3767 break;
3768 case Atom::SidebarLeft:
3769 break;
3770 case Atom::SidebarRight:
3771 break;
3772 case Atom::SnippetCommand:
3773 newLine();
3774 concatenate("\\snippet ");
3775 break;
3776 case Atom::SnippetIdentifier:
3777 newText += next->string();
3778 lineLength += next->string().size();
3779 newLine();
3780 break;
3781 case Atom::SnippetLocation:
3782 newText += next->string() + " ";
3783 lineLength += next->string().size() + 1;
3784 break;
3785 case Atom::String:
3786 wrap(next->string());
3787 break;
3788 case Atom::TableLeft:
3789 {
3790 bool nested = false;
3791 if (structs.isEmpty()) {
3792 const Atom* i = next->next();
3793 while (i->type() != Atom::TableRight) {
3794 if ((i->type() == Atom::ListLeft) ||
3795 (i->type() == Atom::TableLeft)) {
3796 nested = true;
3797 break;
3798 }
3799 i = i->next();
3800 }
3801 }
3802 else
3803 nested = true;
3804 StructDesc d(Table,nested);
3805 structs.push(d);
3806 if (next->string().isEmpty())
3807 concatenate("<table>");
3808 else {
3809 QString attrs = "width=\"" + next->string() + "\"";
3810 attrs += " align=\"center\"";
3811 concatenate("<table " + attrs + ">");
3812 }
3813 newLine();
3814 }
3815 break;
3816 case Atom::TableRight:
3817 concatenate("</table>");
3818 if (!structs.isEmpty())
3819 structs.pop();
3820 newLine();
3821 break;
3822 case Atom::TableHeaderLeft:
3823 concatenate("<tr>");
3824 if (!structs.isEmpty())
3825 structs.top().inTableHeader = true;
3826 newLine();
3827 break;
3828 case Atom::TableHeaderRight:
3829 concatenate("</tr>");
3830 if (!structs.isEmpty())
3831 structs.top().inTableHeader = false;
3832 newLine();
3833 break;
3834 case Atom::TableRowLeft:
3835 if (!structs.isEmpty()) {
3836 structs.top().inTableRow = true;
3837 concatenate("<tr valign=\"top\" class=\"");
3838 if (structs.top().odd)
3839 concatenate("odd\">");
3840 else
3841 concatenate("even\">");
3842 structs.top().odd = !structs.top().odd;
3843 }
3844 newLine();
3845 break;
3846 case Atom::TableRowRight:
3847 concatenate("</tr>");
3848 if (!structs.isEmpty())
3849 structs.top().inTableRow = false;
3850 newLine();
3851 break;
3852 case Atom::TableItemLeft:
3853 if (!structs.isEmpty()) {
3854 structs.top().inTableItem = true;
3855 concatenate("<td>");
3856 if (structs.top().inTableHeader)
3857 concatenate("<b> ");
3858 }
3859 break;
3860 case Atom::TableItemRight:
3861 if (!structs.isEmpty()) {
3862 structs.top().inTableItem = false;
3863 if (structs.top().inTableHeader)
3864 concatenate(" </b>");
3865 concatenate("</td>");
3866 }
3867 newLine();
3868 break;
3869 case Atom::TableOfContents:
3870 break;
3871 case Atom::Target:
3872 {
3873 QString text = next->string();
3874 text.remove(ws_rx);
3875 newLine();
3876 concatenate("\\anchor ");
3877 newText += text;
3878 lineLength += text.size();
3879 newLine();
3880 }
3881 break;
3882 case Atom::UnhandledFormat:
3883 unhandled(next);
3884 break;
3885 case Atom::UnknownCommand:
3886 unhandled(next);
3887 break;
3888 default:
3889 //next->dump();
3890 break;
3891 }
3892 prev = next;
3893 next = next->next();
3894 }
3895}
3896
3897/*!
3898
3899 Pass one looks for topic commands and target and section
3900 commands, and maybe other stuff. These are serialized to
3901 text files, which are read back in by pass2().
3902 */
3903void DoxWriter::pass1()
3904{
3905 QCommandMap& metaCmdMap = priv->metaCommandMap;
3906 if (!metaCmdMap.isEmpty()) {
3907 int c;
3908 QCommandMap::iterator cmd;
3909 if ((cmd = metaCmdMap.find("enum")) != metaCmdMap.end()) {
3910 commentType = EnumComment;
3911 currentEnum = cmd.value()[0];
3912 if ((c = currentEnum.lastIndexOf("::")) > 0) {
3913 currentClass = currentEnum.left(c);
3914 currentEnum = currentEnum.right(currentEnum.size()-c-2);
3915 qDebug() << "currentEnum =" << currentEnum;
3916 qDebug() << "currentClass =" << currentClass;
3917 if (enums.contains(currentEnum,currentClass)) {
3918 qWarning() << "DoxWriter::pass1():"
3919 << "Duplicate enum:"
3920 << currentClass << currentEnum;
3921 }
3922 else
3923 enums.insert(currentEnum,currentClass);
3924 }
3925 }
3926 else if ((cmd = metaCmdMap.find("property")) != metaCmdMap.end()) {
3927 commentType = PropertyComment;
3928 currentClass = cmd.value()[0];
3929 if ((c = currentClass.lastIndexOf("::")) > 0) {
3930 currentProperty = currentClass.right(currentClass.size()-c-2);
3931 currentClass = currentClass.left(c);
3932 qDebug() << "currentProperty =" << currentProperty;
3933 qDebug() << "currentClass =" << currentClass;
3934 if (properties.contains(currentProperty,currentClass)) {
3935 qWarning() << "DoxWriter::pass1():"
3936 << "Duplicate property:"
3937 << currentClass << currentProperty;
3938 }
3939 else
3940 properties.insert(currentProperty,currentClass);
3941 }
3942 }
3943 else if ((cmd = metaCmdMap.find("variable")) != metaCmdMap.end()) {
3944 commentType = VariableComment;
3945 currentClass = cmd.value()[0];
3946 if ((c = currentClass.lastIndexOf("::")) > 0) {
3947 currentVariable = currentClass.right(currentClass.size()-c-2);
3948 currentClass = currentClass.left(c);
3949 qDebug() << "currentVariable =" << currentVariable;
3950 qDebug() << "currentClass =" << currentClass;
3951 if (variables.contains(currentVariable,currentClass)) {
3952 qWarning() << "DoxWriter::pass1():"
3953 << "Duplicate variable:"
3954 << currentClass << currentVariable;
3955 }
3956 else
3957 variables.insert(currentVariable,currentClass);
3958 }
3959 }
3960 }
3961
3962 /*
3963 */
3964 const Atom* next = priv->text.firstAtom();
3965 while (next != 0) {
3966 switch (next->type()) {
3967 case Atom::SectionHeadingLeft:
3968 {
3969 QString text;
3970 next = next->next();
3971 while (next) {
3972 if (next->type() == Atom::SectionHeadingRight)
3973 break;
3974 else
3975 text += next->string();
3976 next = next->next();
3977 }
3978 //text.remove(ws_rx);
3979 insertAnchor(text);
3980 }
3981 break;
3982 case Atom::Target:
3983 {
3984 QString text = next->string();
3985 //text.remove(ws_rx);
3986 insertAnchor(text);
3987 }
3988 default:
3989 break;
3990 }
3991 next = next->next();
3992 }
3993}
3994
3995/*!
3996 Output a parsed, tokenized qdoc comment as a doxygen
3997 comment in diff format for input to the patch command.
3998 */
3999void DoxWriter::pass2()
4000{
4001 if (!conversionRequired()) {
4002 qDebug() << "NO CONVERSION - FILE:" << priv->start_loc.fileName()
4003 << "START:" << priv->start_loc.lineNo()
4004 << "END:" << priv->end_loc.lineNo() - 1;
4005 return;
4006 }
4007
4008 /*
4009 Transformation to doxygen required...
4010 */
4011 newText = "\n/*! \n";
4012 convertMetaCommands();
4013 convertText();
4014 if (newText[newText.size()-1] == ' ')
4015 newText.remove(newText.size()-1,1);
4016 newText += " */\n";
4017 qDebug() << "CONVERTED COMMENT - FILE:" << priv->start_loc.fileName()
4018 << "START:" << priv->start_loc.lineNo()
4019 << "END:" << priv->end_loc.lineNo() - 1;
4020 qDebug() << newText;
4021}
4022
4023/*!
4024 Unparse the second parameter of a "\l" command.
4025 */
4026const Atom* DoxWriter::link(const Atom* atom)
4027{
4028 QString first_text = atom->string();
4029 QString second_text;
4030 const QString* value = 0;
4031
4032 const Atom* next = atom->next(Atom::FormattingLeft,Atom::LINK_);
4033 if (next) {
4034 next->dump();
4035 while (1) {
4036 next = next->next();
4037 next->dump();
4038 if (next->type() == Atom::FormattingRight) {
4039 if (next->string() == Atom::LINK_)
4040 break;
4041 else {
4042 // ignore it.
4043 }
4044 }
4045 else
4046 second_text += next->string();
4047 }
4048 int i = first_text.indexOf('#');
4049 if (i >= 0)
4050 first_text = first_text.right(first_text.size() - i - 1);
4051 //newLine();
4052 if ((value = getExternalPage(first_text))) {
4053 //qDebug() << "USED AN EXTERNAL PAGE TITLE" << first_text;
4054 QString href = "<a href=\""+*value+"\">"+first_text+"</a>";
4055 concatenate(href);
4056 }
4057 else if (first_text.startsWith("http:",Qt::CaseInsensitive)) {
4058 if (first_text == second_text) {
4059 concatenate(first_text);
4060 }
4061 else {
4062 QString href = "<a href=\""+first_text+"\">"+second_text+"</a>";
4063 concatenate(href);
4064 }
4065 }
4066 else if ((value = getPageFile(first_text))) {
4067 //qDebug() << "USED A PAGE TITLE" << first_text;
4068 QStringList parts = (*value).split('.');
4069 QString ref = "\\ref " + parts[0] + " \"" + second_text + "\"";
4070 concatenate(ref);
4071 }
4072 else if ((value = getGroup(first_text))) {
4073 //qDebug() << "USED A GROUP TITLE" << first_text;
4074 concatenate("\\ref " + *value + " \"" + second_text + "\"");
4075 }
4076 else if ((value = getModule(first_text))) {
4077 //qDebug() << "USED A MODULE TITLE" << first_text;
4078 concatenate("\\ref " + *value + " \"" + second_text + "\"");
4079 }
4080 else if ((value = getExamplePath(first_text))) {
4081 //qDebug() << "USED AN EXAMPLE TITLE" << first_text;
4082 first_text.remove(ws_rx);
4083 QString ref = "\\ref " + first_text + " \"" + second_text + "\"";
4084 concatenate(ref);
4085 }
4086 else if ((value = getFile(first_text))) {
4087 //qDebug() << "USED A FILE TITLE" << first_text;
4088 // I think this command is no longer available.
4089 first_text.remove(ws_rx);
4090 QString ref = "\\ref " + first_text + " \"" + second_text + "\"";
4091 concatenate(ref);
4092 }
4093 else if ((value = getHeaderFile(first_text))) {
4094 //qDebug() << "USED A HEADER FILE TITLE" << first_text;
4095 first_text.remove(ws_rx);
4096 QString ref = "\\ref " + first_text + " \"" + second_text + "\"";
4097 concatenate(ref);
4098 }
4099 else if (isAnchor(first_text)) {
4100 //qDebug() << "USED AN ANCHOR" << first_text;
4101 first_text.remove(ws_rx);
4102 QString ref = "\\ref " + first_text + " \"" + second_text + "\"";
4103 concatenate(ref);
4104 }
4105 else if ((value = getPageTitle(first_text))) {
4106 //qDebug() << "USED AN INVERSE PAGE TITLE" << first_text;
4107 QStringList parts = first_text.split('.');
4108 QString ref = "\\ref " + parts[0] + " \"" + second_text + "\"";
4109 concatenate(ref);
4110 }
4111 else if ((value = getExampleTitle(first_text))) {
4112 //qDebug() << "USED AN INVERSE EXAMPLE TITLE" << first_text;
4113 QString title = *value;
4114 title.remove(ws_rx);
4115 QString ref = "\\ref " + title + " \"" + second_text + "\"";
4116 concatenate(ref);
4117 }
4118 else if ((value = getGroupTitle(first_text))) {
4119 //qDebug() << "USED AN INVERSE GROUP TITLE" << first_text;
4120 concatenate("\\ref " + first_text + " \"" + second_text + "\"");
4121 }
4122 else if ((value = getModuleTitle(first_text))) {
4123 //qDebug() << "USED AN INVERSE MODULE TITLE" << first_text;
4124 concatenate("\\ref " + first_text + " \"" + second_text + "\"");
4125 }
4126 else if ((value = getFileTitle(first_text))) {
4127 qDebug() << "USED AN INVERSE FILE TITLE" << first_text;
4128 }
4129 else if ((value = getHeaderFileTitle(first_text))) {
4130 qDebug() << "USED AN INVERSE HEADER FILE TITLE" << first_text;
4131 }
4132 else if ((first_text.indexOf("::") >= 0) ||
4133 (first_text.indexOf("()") >= 0) ||
4134 (first_text[0] == 'Q')) {
4135 //qDebug() << "AUTO-LINKABLE" << first_text;
4136 if (first_text == second_text)
4137 concatenate(first_text);
4138 else {
4139 QString link = first_text + " " + second_text;
4140 concatenate("\\link " + link + "\\endlink");
4141 }
4142 }
4143 else {
4144 QString link;
4145 QStringList propertyClasses;
4146 QStringList variableClasses;
4147 QStringList enumClasses;
4148 bool p = isProperty(first_text,propertyClasses);
4149 bool v = isVariable(first_text,variableClasses);
4150 bool e = isEnum(first_text,enumClasses);
4151 if (e) {
4152 if (enumClasses.size() == 1)
4153 link = enumClasses[0];
4154 else if (enumClasses.contains(currentClass))
4155 link = currentClass;
4156 else {
4157 QString msg = "Unqualified enum name: " + first_text;
4158 QString details = "Classes: " + enumClasses.join(", ");
4159 priv->start_loc.error(msg,details);
4160 }
4161 if (!link.isEmpty())
4162 qDebug() << "FOUND ENUM" << link << first_text;
4163 }
4164 else if (p && v) {
4165 if (propertyClasses.size() == 1) {
4166 if (variableClasses.size() == 1) {
4167 if (propertyClasses[0] == variableClasses[0])
4168 link = propertyClasses[0];
4169 }
4170 }
4171 if (link.isEmpty()) {
4172 if (propertyClasses.contains(currentClass) ||
4173 variableClasses.contains(currentClass))
4174 link = currentClass;
4175 else {
4176 propertyClasses += variableClasses;
4177 QString msg = "Unqualified property or variable name: "
4178 + first_text;
4179 QString details = "Classes: " +
4180 propertyClasses.join(", ");
4181 priv->start_loc.error(msg,details);
4182 }
4183 }
4184 }
4185 else if (p) {
4186 if (propertyClasses.size() == 1)
4187 link = propertyClasses[0];
4188 else if (propertyClasses.contains(currentClass))
4189 link = currentClass;
4190 else {
4191 QString msg = "Unqualified property name: " + first_text;
4192 QString details = "Classes: " + propertyClasses.join(", ");
4193 priv->start_loc.error(msg,details);
4194 }
4195 }
4196 else if (v) {
4197 if (variableClasses.size() == 1)
4198 link = variableClasses[0];
4199 else if (variableClasses.contains(currentClass))
4200 link = currentClass;
4201 else {
4202 QString msg = "Unqualified variable name: " + first_text;
4203 QString details = "Classes: " + variableClasses.join(", ");
4204 priv->start_loc.error(msg,details);
4205 }
4206 }
4207 else {
4208 qDebug() << "NOT AUTO-LINKABLE" << first_text;
4209 QString s = first_text + " " + second_text;
4210 concatenate("\\link " + s + "\\endlink");
4211 }
4212 if (!link.isEmpty()) {
4213 link += "::" + first_text + " " + second_text;
4214 concatenate("\\link " + link + "\\endlink");
4215 }
4216 }
4217 }
4218 else
4219 qDebug() << "LINK with no second parameter!!!!";
4220 return next? next : atom;
4221}
4222
4223/*!
4224 If the current line length is 0, the current line is
4225 indented according to the context.
4226 */
4227void DoxWriter::indentLine()
4228{
4229 if (lineLength == 0) {
4230 newText += DOXYGEN_INDENT_STRING;
4231 lineLength = DOXYGEN_INDENT;
4232 if (!structs.isEmpty()) {
4233 for (int i=1; i<structs.size(); ++i) {
4234 newText += DOXYGEN_TAB_STRING;
4235 lineLength += DOXYGEN_TAB_SIZE;
4236 }
4237 }
4238 }
4239}
4240
4241/*!
4242 Concatenates a newline to the doxygen text, increments the
4243 line count, and resets the line length to 0.
4244 */
4245void DoxWriter::newLine()
4246{
4247 newText += "\n";
4248 ++lineCount;
4249 lineLength = 0;
4250}
4251
4252static const int maxLineLength = 70;
4253
4254/*!
4255 Concatenate the \a text to the doxygen comment currently
4256 under construction and increment the current line length
4257 by the size of the \a text.
4258
4259 If incrementing the current line length by the \a text size
4260 would make the current line length longer than the maximum
4261 line length, then call newLine() and indentLine() \e before
4262 concatenating the \a text.
4263 */
4264void DoxWriter::concatenate(QString text)
4265{
4266 if ((lineLength + text.size()) > maxLineLength)
4267 newLine();
4268 indentLine();
4269 newText += text;
4270 lineLength += text.size();
4271}
4272
4273static bool punctuation(QChar c)
4274{
4275 switch (c.toAscii()) {
4276 case '.':
4277 case ',':
4278 case ':':
4279 case ';':
4280 case '/':
4281 case '+':
4282 case '-':
4283 case '?':
4284 case '!':
4285 case '\"':
4286 return true;
4287 default:
4288 break;
4289 }
4290 return false;
4291}
4292
4293/*!
4294 Concatenate the \a text string to the doxygen text, doing
4295 line wrapping where necessary.
4296 */
4297void DoxWriter::wrap(QString text)
4298{
4299 int from = 0;
4300 int to = -1;
4301
4302 if ((lineLength == 0) || (lineLength >= maxLineLength)) {
4303 if (!text.isEmpty() && (text[0] == ' '))
4304 text = text.right(text.size() - 1);
4305 }
4306
4307 indentLine();
4308 while (text.size()) {
4309 int avail = maxLineLength - lineLength;
4310 from = text.indexOf(' ',from);
4311 if (from >= 0) {
4312 if (from < avail)
4313 to = from++;
4314 else if (from == 1 && punctuation(text[0]))
4315 to = from++;
4316 else {
4317 if (to >= 0) {
4318 newText += text.left(to+1);
4319 lineLength += to + 1;
4320 text = text.right(text.size() - to - 1);
4321 }
4322 else {
4323 newLine();
4324 indentLine();
4325 newText += text.left(from+1);
4326 lineLength += from + 1;
4327 text = text.right(text.size() - from - 1);
4328 }
4329 from = 0;
4330 to = -1;
4331 if (text.size() && (lineLength > maxLineLength)) {
4332 newLine();
4333 indentLine();
4334 }
4335 }
4336 }
4337 else
4338 break;
4339 }
4340 if (text.size()) {
4341 if (lineLength >= maxLineLength) {
4342 newLine();
4343 indentLine();
4344 }
4345 newText += text;
4346 lineLength += text.size();
4347 }
4348}
4349
4350/*!
4351 This will output something, but it depends on what the
4352 \a atom string and the \a next atom string are.
4353 */
4354void DoxWriter::formattingLeft(const Atom* atom, const Atom* next)
4355{
4356 if (atom->string() == "parameter") {
4357 concatenate("\\a ");
4358 return;
4359 }
4360 else if (atom->string() == "underline") {
4361 concatenate("<u>");
4362 return;
4363 }
4364 else if (atom->string() == "superscript") {
4365 concatenate("<sup>");
4366 return;
4367 }
4368 else if (atom->string() == "subscript") {
4369 concatenate("<sub>");
4370 return;
4371 }
4372 int ws = -1;
4373 if (next)
4374 ws = next->string().indexOf(ws_rx);
4375 if (atom->string() == "bold") {
4376 if (ws < 0)
4377 concatenate("\\b ");
4378 else
4379 concatenate("<b>");
4380 }
4381 else if (atom->string() == "italic") {
4382 if (ws < 0)
4383 concatenate("\\e ");
4384 else
4385 concatenate("<i>");
4386 }
4387 else if (atom->string() == "teletype") {
4388 if (ws < 0)
4389 concatenate("\\c ");
4390 else
4391 concatenate("<tt>");
4392 }
4393 else
4394 qDebug() << "UNHANDLED FormattingLeft: " << atom->string();
4395}
4396
4397/*!
4398 This will output something, but it depends on what the
4399 \a atom string and the \a prev atom string are.
4400 */
4401void DoxWriter::formattingRight(const Atom* atom, const Atom* prev)
4402{
4403 if (atom->string() == "parameter")
4404 return;
4405 else if (atom->string() == "underline") {
4406 concatenate("</u>");
4407 return;
4408 }
4409 else if (atom->string() == "superscript") {
4410 concatenate("</sup>");
4411 return;
4412 }
4413 else if (atom->string() == "subscript") {
4414 concatenate("</sub>");
4415 return;
4416 }
4417 int ws = -1;
4418 if (prev)
4419 ws = prev->string().indexOf(ws_rx);
4420 if (ws < 0)
4421 return;
4422 if (atom->string() == "bold")
4423 concatenate("</b>");
4424 else if (atom->string() == "italic")
4425 concatenate("</i>");
4426 else if (atom->string() == "teletype")
4427 concatenate("</tt>");
4428 else
4429 qDebug() << "UNHANDLED FormattingRight: " << atom->string();
4430}
4431
4432/*!
4433 Output a \c or a <tt>...</tt>.
4434 */
4435void DoxWriter::tt(const Atom* atom)
4436{
4437 if (atom->string().indexOf(ws_rx) < 0) {
4438 concatenate("\\c ");
4439 concatenate(atom->string());
4440 }
4441 else {
4442 concatenate("<tt>");
4443 concatenate(atom->string());
4444 concatenate("</tt>");
4445 }
4446}
4447
4448/*!
4449 */
4450void DoxWriter::formatIf(const Atom* atom)
4451{
4452 if (atom->string() == "HTML") {
4453 newLine();
4454 concatenate("\\htmlonly");
4455 newLine();
4456 }
4457}
4458
4459/*!
4460 */
4461void DoxWriter::formatEndif()
4462{
4463 newLine();
4464 concatenate("\\endhtmlonly");
4465 newLine();
4466}
4467
4468/*!
4469 */
4470void DoxWriter::formatElse()
4471{
4472 // nothing.
4473}
4474
4475/*!
4476 Pass 1: Construct a section identifier and insert it into
4477 the anchor set.
4478
4479 Pass 2: Convert section1, section2, and section3 commands
4480 to section, subsection, and subsubsection respectively.
4481 Warn if a section command higher than 3 is seen.
4482 */
4483const Atom* DoxWriter::sectionHeading(const Atom* atom)
4484{
4485 QString heading_level = atom->string();
4486 QString heading_text;
4487 const Atom* next = atom->next();
4488 while (next) {
4489 next->dump();
4490 if (next->type() == Atom::SectionHeadingRight) {
4491 if (next->string() == heading_level)
4492 break;
4493 else {
4494 qDebug() << "WRONG SectionHeading number!!!!";
4495 }
4496 }
4497 else
4498 heading_text += next->string();
4499 next = next->next();
4500 }
4501
4502 QString heading_identifier = heading_text;
4503 heading_identifier.remove(ws_rx);
4504
4505 newLine();
4506 if (heading_level == "1")
4507 heading_level = "\\section ";
4508 else if (heading_level == "2")
4509 heading_level = "\\subsection ";
4510 else if (heading_level == "3")
4511 heading_level = "\\subsubsection ";
4512 else if (heading_level == "4") {
4513 heading_level = "\\subsubsection ";
4514 qDebug() << "WARNING section4 converted to \\subsubsection";
4515 }
4516 else {
4517 heading_level = "\\subsubsection ";
4518 qDebug() << "WARNING section5 converted to \\subsubsection";
4519 }
4520 concatenate(heading_level);
4521 newText += heading_identifier + " ";
4522 lineLength += heading_identifier.size() + 1;
4523 newText += heading_text;
4524 lineLength += heading_text.size();
4525 newLine();
4526 return next? next : atom;
4527}
4528
4529/*!
4530 Report an unhandled atom.
4531 */
4532void DoxWriter::unhandled(const Atom* atom)
4533{
4534 qDebug() << "UNHANDLED ATOM";
4535 atom->dump();
4536}
4537
4538/*!
4539 Output a code/endcode block.
4540 */
4541void DoxWriter::code(const Atom* atom)
4542{
4543 newLine();
4544 concatenate("\\code");
4545 writeCode(atom->string());
4546 concatenate("\\endcode");
4547 newLine();
4548}
4549
4550/*!
4551 Output a code/endcode block depending on the
4552 CodeQuote Command and CodeQuoteArgument parameters.
4553 */
4554const Atom* DoxWriter::codeQuoteCommand(const Atom* atom)
4555{
4556 QString command = atom->string();
4557 atom = atom->next();
4558 concatenate("\\code");
4559 if (command == "codeline") {
4560 newLine();
4561 concatenate(atom->string());
4562 newLine();
4563 }
4564 else if (command == "dots") {
4565 newLine();
4566 concatenate(atom->string());
4567 newLine();
4568 }
4569 else {
4570 writeCode(atom->string());
4571 }
4572 concatenate("\\endcode");
4573 return atom;
4574}
4575
4576/*!
4577 Appends a block of code to the comment.
4578 */
4579void DoxWriter::writeCode(QString text)
4580{
4581 int cr_count = text.count('\n') - 1;
4582 if (cr_count >= 0) {
4583 int last_cr = text.lastIndexOf('\n');
4584 newText += text.left(last_cr);
4585 lineCount += cr_count;
4586 }
4587 else
4588 newText += text;
4589 newLine();
4590}
4591
4592/*!
4593 Inserts \a text into the anchor set. This function is called
4594 during doxygen pass 1.
4595 */
4596void DoxWriter::insertAnchor(const QString& text)
4597{
4598 anchors.insert(text);
4599}
4600
4601/*!
4602 Returns true if \a text identifies an anchor, section,
4603 subsection, subsubsection, or page.
4604 */
4605bool DoxWriter::isAnchor(const QString& text)
4606{
4607 return anchors.contains(text);
4608}
4609
4610/*!
4611 Write the set of anchors to a file, one per line.
4612 */
4613void DoxWriter::writeAnchors()
4614{
4615 QFile file("anchors.txt");
4616 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
4617 qWarning("Unable to open anchors.txt for writing.");
4618 return;
4619 }
4620
4621 QTextStream out(&file);
4622 QSet<QString>::const_iterator i = anchors.constBegin();
4623 while (i != anchors.constEnd()) {
4624 out << *i << "\n";
4625 ++i;
4626 }
4627 file.close();
4628}
4629
4630/*!
4631 Read the set of anchors from the anchors file, one per line,
4632 and insert each one into the anchor set.
4633 */
4634void DoxWriter::readAnchors()
4635{
4636 QFile file("anchors.txt");
4637 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
4638 qWarning("Unable to open anchors.txt for reading.");
4639 return;
4640 }
4641
4642 QTextStream in(&file);
4643 while (!in.atEnd()) {
4644 QString line = in.readLine();
4645 anchors.insert(line);
4646 }
4647 file.close();
4648#if 0
4649 QSet<QString>::const_iterator i = anchors.constBegin();
4650 while (i != anchors.constEnd()) {
4651 qDebug() << *i;
4652 ++i;
4653 }
4654#endif
4655}
4656
4657/*!
4658 Inserts \a title into one of the title maps. \a title is
4659 mapped to the \a node name. This function is called during
4660 doxygen pass 1.
4661 */
4662void DoxWriter::insertTitle(FakeNode* node, const QString& title)
4663{
4664 switch (node->subType()) {
4665 case FakeNode::Example:
4666 if (exampleTitles.contains(title)) {
4667 qWarning() << "DoxWriter::insertTitle():"
4668 << "Duplicate example title:"
4669 << title;
4670 }
4671 else {
4672 exampleTitles[title] = node->name();
4673 exampleTitlesInverse[node->name()] = title;
4674 }
4675 break;
4676 case FakeNode::HeaderFile:
4677 if (headerFileTitles.contains(title)) {
4678 qWarning() << "DoxWriter::insertTitle():"
4679 << "Duplicate header file title:"
4680 << title;
4681 }
4682 else {
4683 headerFileTitles[title] = node->name();
4684 headerFileTitlesInverse[node->name()] = title;
4685 }
4686 break;
4687 case FakeNode::File:
4688 if (fileTitles.contains(title)) {
4689 qWarning() << "DoxWriter::insertTitle():"
4690 << "Duplicate file title:"
4691 << title;
4692 }
4693 else {
4694 fileTitles[title] = node->name();
4695 fileTitlesInverse[node->name()] = title;
4696 }
4697 break;
4698 case FakeNode::Group:
4699 if (groupTitles.contains(title)) {
4700 qWarning() << "DoxWriter::insertTitle():"
4701 << "Duplicate group title:"
4702 << title;
4703 }
4704 else {
4705 groupTitles[title] = node->name();
4706 groupTitlesInverse[node->name()] = title;
4707 }
4708 break;
4709 case FakeNode::Module:
4710 if (moduleTitles.contains(title)) {
4711 qWarning() << "DoxWriter::insertTitle():"
4712 << "Duplicate module title:"
4713 << title;
4714 }
4715 else {
4716 moduleTitles[title] = node->name();
4717 moduleTitlesInverse[node->name()] = title;
4718 }
4719 break;
4720 case FakeNode::Page:
4721 if (pageTitles.contains(title)) {
4722 qWarning() << "DoxWriter::insertTitle():"
4723 << "Duplicate page title:"
4724 << title;
4725 }
4726 else {
4727 pageTitles[title] = node->name();
4728 pageTitlesInverse[node->name()] = title;
4729 }
4730 break;
4731 case FakeNode::ExternalPage:
4732 if (externalPageTitles.contains(title)) {
4733 qWarning() << "DoxWriter::insertTitle():"
4734 << "Duplicate external page title:"
4735 << title;
4736 }
4737 else {
4738 externalPageTitles[title] = node->name();
4739 externalPageTitlesInverse[node->name()] = title;
4740 }
4741 break;
4742 default:
4743 break;
4744 }
4745}
4746
4747/*!
4748 */
4749const QString* DoxWriter::getPageFile(const QString& title)
4750{
4751 QStringMapEntry entry = pageTitles.find(title);
4752 return (entry == pageTitles.end()) ? 0 : &entry.value();
4753}
4754
4755/*!
4756 */
4757const QString* DoxWriter::getExamplePath(const QString& title)
4758{
4759 QStringMapEntry entry = exampleTitles.find(title);
4760 return (entry == exampleTitles.end()) ? 0 : &entry.value();
4761}
4762
4763/*!
4764 */
4765const QString* DoxWriter::getFile(const QString& title)
4766{
4767 QStringMapEntry entry = fileTitles.find(title);
4768 return (entry == fileTitles.end()) ? 0 : &entry.value();
4769}
4770
4771/*!
4772 */
4773const QString* DoxWriter::getHeaderFile(const QString& title)
4774{
4775 QStringMapEntry entry = headerFileTitles.find(title);
4776 return (entry == headerFileTitles.end()) ? 0 : &entry.value();
4777}
4778
4779/*!
4780 */
4781const QString* DoxWriter::getGroup(const QString& title)
4782{
4783 QStringMapEntry entry = groupTitles.find(title);
4784 return (entry == groupTitles.end()) ? 0 : &entry.value();
4785}
4786
4787/*!
4788 */
4789const QString* DoxWriter::getModule(const QString& title)
4790{
4791 QStringMapEntry entry = moduleTitles.find(title);
4792 return (entry == moduleTitles.end()) ? 0 : &entry.value();
4793}
4794
4795/*!
4796 */
4797const QString* DoxWriter::getExternalPage(const QString& title)
4798{
4799 QStringMapEntry entry = externalPageTitles.find(title);
4800 return (entry == externalPageTitles.end()) ? 0 : &entry.value();
4801}
4802
4803/*!
4804 */
4805const QString* DoxWriter::getPageTitle(const QString& text)
4806{
4807 QStringMapEntry entry = pageTitlesInverse.find(text);
4808 return (entry == pageTitlesInverse.end()) ? 0 : &entry.value();
4809}
4810
4811/*!
4812 */
4813const QString* DoxWriter::getExampleTitle(const QString& text)
4814{
4815 QStringMapEntry entry = exampleTitlesInverse.find(text);
4816 return (entry == exampleTitlesInverse.end()) ? 0 : &entry.value();
4817}
4818
4819/*!
4820 */
4821const QString* DoxWriter::getFileTitle(const QString& text)
4822{
4823 QStringMapEntry entry = fileTitlesInverse.find(text);
4824 return (entry == fileTitlesInverse.end()) ? 0 : &entry.value();
4825}
4826
4827/*!
4828 */
4829const QString* DoxWriter::getHeaderFileTitle(const QString& text)
4830{
4831 QStringMapEntry entry = headerFileTitlesInverse.find(text);
4832 return (entry == headerFileTitlesInverse.end()) ? 0 : &entry.value();
4833}
4834
4835/*!
4836 */
4837const QString* DoxWriter::getGroupTitle(const QString& text)
4838{
4839 QStringMapEntry entry = groupTitlesInverse.find(text);
4840 return (entry == groupTitlesInverse.end()) ? 0 : &entry.value();
4841}
4842
4843/*!
4844 */
4845const QString* DoxWriter::getModuleTitle(const QString& text)
4846{
4847 QStringMapEntry entry = moduleTitlesInverse.find(text);
4848 return (entry == moduleTitlesInverse.end()) ? 0 : &entry.value();
4849}
4850
4851/*!
4852 */
4853const QString* DoxWriter::getExternalPageTitle(const QString& text)
4854{
4855 QStringMapEntry entry = externalPageTitlesInverse.find(text);
4856 return (entry == externalPageTitlesInverse.end()) ? 0 : &entry.value();
4857}
4858
4859/*!
4860 Serialize \a map to file \a name.
4861 */
4862void DoxWriter::writeMap(const QStringMap& map, const QString& name)
4863{
4864
4865 QFile file(name);
4866 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
4867 qWarning() << "Unable to open" << name << "for writing.";
4868 return;
4869 }
4870
4871 QTextStream out(&file);
4872 QStringMap::const_iterator i = map.constBegin();
4873 while (i != map.constEnd()) {
4874 out << i.key() << "\n";
4875 out << i.value() << "\n";
4876 ++i;
4877 }
4878 file.close();
4879}
4880
4881/*!
4882 Read file \a name into the \a map.
4883 */
4884void DoxWriter::readMap(QStringMap& map, QStringMap& inverseMap, const QString& name)
4885{
4886 QFile file(name);
4887 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
4888 qWarning() << "Unable to open" << name << "for reading.";
4889 return;
4890 }
4891
4892 QTextStream in(&file);
4893 while (!in.atEnd()) {
4894 QString title = in.readLine();
4895 QString value = in.readLine();
4896 map[title] = value;
4897 inverseMap[value] = title;
4898 }
4899 file.close();
4900}
4901
4902/*!
4903 Write the sets of titles to text files, one per line.
4904 */
4905void DoxWriter::writeTitles()
4906{
4907 if (!pageTitles.isEmpty())
4908 writeMap(pageTitles,"pagetitles.txt");
4909 if (!fileTitles.isEmpty())
4910 writeMap(fileTitles,"filetitles.txt");
4911 if (!headerFileTitles.isEmpty())
4912 writeMap(headerFileTitles,"headerfiletitles.txt");
4913 if (!exampleTitles.isEmpty())
4914 writeMap(exampleTitles,"exampletitles.txt");
4915 if (!moduleTitles.isEmpty())
4916 writeMap(moduleTitles,"moduletitles.txt");
4917 if (!groupTitles.isEmpty())
4918 writeMap(groupTitles,"grouptitles.txt");
4919 if (!externalPageTitles.isEmpty())
4920 writeMap(externalPageTitles,"externalpagetitles.txt");
4921}
4922
4923/*!
4924 Read the sets of titles from the titles files, one per line,
4925 and insert each one into the appropriate title set.
4926 */
4927void DoxWriter::readTitles()
4928{
4929 readMap(pageTitles,pageTitlesInverse,"pagetitles.txt");
4930 readMap(fileTitles,fileTitlesInverse,"filetitles.txt");
4931 readMap(headerFileTitles,headerFileTitlesInverse,"headerfiletitles.txt");
4932 readMap(exampleTitles,exampleTitlesInverse,"exampletitles.txt");
4933 readMap(moduleTitles,moduleTitlesInverse,"moduletitles.txt");
4934 readMap(groupTitles,groupTitlesInverse,"grouptitles.txt");
4935 readMap(externalPageTitles,
4936 externalPageTitlesInverse,
4937 "externalpagetitles.txt");
4938}
4939
4940/*!
4941 Serialize \a map to file \a name.
4942 */
4943void DoxWriter::writeMultiMap(const QStringMultiMap& map, const QString& name)
4944{
4945
4946 QFile file(name);
4947 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
4948 qWarning() << "Unable to open" << name << "for writing.";
4949 return;
4950 }
4951
4952 QTextStream out(&file);
4953 QStringMultiMap::const_iterator i = map.constBegin();
4954 while (i != map.constEnd()) {
4955 out << i.key() << "\n";
4956 out << i.value() << "\n";
4957 ++i;
4958 }
4959 file.close();
4960}
4961
4962/*!
4963 Write the4 property names and variable names to text files.
4964 */
4965void DoxWriter::writeMembers()
4966{
4967 if (!variables.isEmpty())
4968 writeMultiMap(variables,"variables.txt");
4969 if (!properties.isEmpty())
4970 writeMultiMap(properties,"properties.txt");
4971 if (!enums.isEmpty())
4972 writeMultiMap(enums,"enums.txt");
4973}
4974
4975/*!
4976 Read file \a name into the \a map.
4977 */
4978void DoxWriter::readMultiMap(QStringMultiMap& map, const QString& name)
4979{
4980 QFile file(name);
4981 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
4982 qWarning() << "Unable to open" << name << "for reading.";
4983 return;
4984 }
4985
4986 QTextStream in(&file);
4987 while (!in.atEnd()) {
4988 QString member = in.readLine();
4989 QString className = in.readLine();
4990 map.insert(member,className);
4991 }
4992 file.close();
4993}
4994
4995/*!
4996 Read the property names and variable names from the test files.
4997 */
4998void DoxWriter::readMembers()
4999{
5000 readMultiMap(variables,"variables.txt");
5001 readMultiMap(properties,"properties.txt");
5002 readMultiMap(enums,"enums.txt");
5003}
5004
5005/*!
5006 Return true if \a name is a property. Loads \a classes with
5007 the names of all the classes in which \a name is a property.
5008 */
5009bool DoxWriter::isProperty(const QString& name, QStringList& classes)
5010{
5011 classes = properties.values(name);
5012 return !classes.isEmpty();
5013}
5014
5015/*!
5016 Return true if \a name is a variable. Loads \a classes with
5017 the names of all the classes in which \a name is a variable.
5018 */
5019bool DoxWriter::isVariable(const QString& name, QStringList& classes)
5020{
5021 classes = variables.values(name);
5022 return !classes.isEmpty();
5023}
5024
5025/*!
5026 Return true if \a name is an enum type. Loads \a classes with
5027 the names of all the classes in which \a name is an enum type.
5028 */
5029bool DoxWriter::isEnum(const QString& name, QStringList& classes)
5030{
5031 classes = enums.values(name);
5032 return !classes.isEmpty();
5033}
5034#endif
5035
5036QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.