b60563c7f3edafaa80746cc1c169c91970b9a645
[profile/ivi/qtbase.git] / src / tools / qdoc / doc.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
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 <qfile.h>
52 #include <qfileinfo.h>
53 #include <qhash.h>
54 #include <qtextstream.h>
55 #include <qregexp.h>
56 #include <ctype.h>
57 #include <limits.h>
58 #include <qdebug.h>
59
60 QT_BEGIN_NAMESPACE
61
62 Q_GLOBAL_STATIC(QSet<QString>, null_Set_QString)
63 Q_GLOBAL_STATIC(TopicList, nullTopicList)
64 Q_GLOBAL_STATIC(QStringList, null_QStringList)
65 Q_GLOBAL_STATIC(QList<Text>, null_QList_Text)
66 //Q_GLOBAL_STATIC(QStringMap, null_QStringMap)
67 Q_GLOBAL_STATIC(QStringMultiMap, null_QStringMultiMap)
68
69 struct Macro
70 {
71     QString defaultDef;
72     Location defaultDefLocation;
73     QStringMap otherDefs;
74     int numParams;
75 };
76
77 enum {
78     CMD_A,
79     CMD_ABSTRACT,
80     CMD_ANNOTATEDLIST,
81     CMD_B,
82     CMD_BADCODE,
83     CMD_BASENAME,
84     CMD_BOLD,
85     CMD_BR,
86     CMD_BRIEF,
87     CMD_C,
88     CMD_CAPTION,
89     CMD_CHAPTER,
90     CMD_CODE,
91     CMD_CODELINE,
92     CMD_DIV,
93     CMD_DOTS,
94     CMD_E,
95     CMD_ELSE,
96     CMD_ENDABSTRACT,
97     CMD_ENDCHAPTER,
98     CMD_ENDCODE,
99     CMD_ENDDIV,
100     CMD_ENDFOOTNOTE,
101     CMD_ENDIF,
102     CMD_ENDLEGALESE,
103     CMD_ENDLINK,
104     CMD_ENDLIST,
105     CMD_ENDMAPREF,
106     CMD_ENDOMIT,
107     CMD_ENDPART,
108     CMD_ENDQUOTATION,
109     CMD_ENDRAW,
110     CMD_ENDSECTION1,
111     CMD_ENDSECTION2,
112     CMD_ENDSECTION3,
113     CMD_ENDSECTION4,
114     CMD_ENDSIDEBAR,
115     CMD_ENDTABLE,
116     CMD_ENDTOPICREF,
117     CMD_FOOTNOTE,
118     CMD_GENERATELIST,
119     CMD_GRANULARITY,
120     CMD_HEADER,
121     CMD_HR,
122     CMD_I,
123     CMD_IF,
124     CMD_IMAGE,
125     CMD_IMPORTANT,
126     CMD_INCLUDE,
127     CMD_INLINEIMAGE,
128     CMD_INDEX,
129     CMD_KEYWORD,
130     CMD_L,
131     CMD_LEGALESE,
132     CMD_LI,
133     CMD_LINK,
134     CMD_LIST,
135     CMD_MAPREF,
136     CMD_META,
137     CMD_NEWCODE,
138     CMD_NOTE,
139     CMD_O,
140     CMD_OLDCODE,
141     CMD_OMIT,
142     CMD_OMITVALUE,
143     CMD_OVERLOAD,
144     CMD_PART,
145     CMD_PRINTLINE,
146     CMD_PRINTTO,
147     CMD_PRINTUNTIL,
148     CMD_QUOTATION,
149     CMD_QUOTEFILE,
150     CMD_QUOTEFROMFILE,
151     CMD_QUOTEFUNCTION,
152     CMD_RAW,
153     CMD_ROW,
154     CMD_SA,
155     CMD_SECTION1,
156     CMD_SECTION2,
157     CMD_SECTION3,
158     CMD_SECTION4,
159     CMD_SIDEBAR,
160     CMD_SINCELIST,
161     CMD_SKIPLINE,
162     CMD_SKIPTO,
163     CMD_SKIPUNTIL,
164     CMD_SNIPPET,
165     CMD_SPAN,
166     CMD_SUB,
167     CMD_SUP,
168     CMD_TABLE,
169     CMD_TABLEOFCONTENTS,
170     CMD_TARGET,
171     CMD_TOPICREF,
172     CMD_TT,
173     CMD_UICONTROL,
174     CMD_UNDERLINE,
175     CMD_UNICODE,
176     CMD_VALUE,
177     CMD_WARNING,
178     CMD_QML,
179     CMD_ENDQML,
180     CMD_CPP,
181     CMD_ENDCPP,
182     CMD_QMLTEXT,
183     CMD_ENDQMLTEXT,
184     CMD_CPPTEXT,
185     CMD_ENDCPPTEXT,
186     CMD_JS,
187     CMD_ENDJS,
188     NOT_A_CMD
189 };
190
191 static struct {
192     const char *english;
193     int no;
194     QString *alias;
195 } cmds[] = {
196     { "a", CMD_A, 0 },
197     { "abstract", CMD_ABSTRACT, 0 },
198     { "annotatedlist", CMD_ANNOTATEDLIST, 0 },
199     { "b", CMD_B, 0 },
200     { "badcode", CMD_BADCODE, 0 },
201     { "basename", CMD_BASENAME, 0 }, // ### don't document for now
202     { "bold", CMD_BOLD, 0 },
203     { "br", CMD_BR, 0 },
204     { "brief", CMD_BRIEF, 0 },
205     { "c", CMD_C, 0 },
206     { "caption", CMD_CAPTION, 0 },
207     { "chapter", CMD_CHAPTER, 0 },
208     { "code", CMD_CODE, 0 },
209     { "codeline", CMD_CODELINE, 0},
210     { "div", CMD_DIV, 0 },
211     { "dots", CMD_DOTS, 0 },
212     { "e", CMD_E, 0 },
213     { "else", CMD_ELSE, 0 },
214     { "endabstract", CMD_ENDABSTRACT, 0 },
215     { "endchapter", CMD_ENDCHAPTER, 0 },
216     { "endcode", CMD_ENDCODE, 0 },
217     { "enddiv", CMD_ENDDIV, 0 },
218     { "endfootnote", CMD_ENDFOOTNOTE, 0 },
219     { "endif", CMD_ENDIF, 0 },
220     { "endlegalese", CMD_ENDLEGALESE, 0 },
221     { "endlink", CMD_ENDLINK, 0 },
222     { "endlist", CMD_ENDLIST, 0 },
223     { "endmapref", CMD_ENDMAPREF, 0 },
224     { "endomit", CMD_ENDOMIT, 0 },
225     { "endpart", CMD_ENDPART, 0 },
226     { "endquotation", CMD_ENDQUOTATION, 0 },
227     { "endraw", CMD_ENDRAW, 0 },
228     { "endsection1", CMD_ENDSECTION1, 0 },  // ### don't document for now
229     { "endsection2", CMD_ENDSECTION2, 0 },  // ### don't document for now
230     { "endsection3", CMD_ENDSECTION3, 0 },  // ### don't document for now
231     { "endsection4", CMD_ENDSECTION4, 0 },  // ### don't document for now
232     { "endsidebar", CMD_ENDSIDEBAR, 0 },
233     { "endtable", CMD_ENDTABLE, 0 },
234     { "endtopicref", CMD_ENDTOPICREF, 0 },
235     { "footnote", CMD_FOOTNOTE, 0 },
236     { "generatelist", CMD_GENERATELIST, 0 },
237     { "granularity", CMD_GRANULARITY, 0 }, // ### don't document for now
238     { "header", CMD_HEADER, 0 },
239     { "hr", CMD_HR, 0 },
240     { "i", CMD_I, 0 },
241     { "if", CMD_IF, 0 },
242     { "image", CMD_IMAGE, 0 },
243     { "important", CMD_IMPORTANT, 0 },
244     { "include", CMD_INCLUDE, 0 },
245     { "inlineimage", CMD_INLINEIMAGE, 0 },
246     { "index", CMD_INDEX, 0 }, // ### don't document for now
247     { "keyword", CMD_KEYWORD, 0 },
248     { "l", CMD_L, 0 },
249     { "legalese", CMD_LEGALESE, 0 },
250     { "li", CMD_LI, 0 },
251     { "link", CMD_LINK, 0 },
252     { "list", CMD_LIST, 0 },
253     { "mapref", CMD_MAPREF, 0 },
254     { "meta", CMD_META, 0 },
255     { "newcode", CMD_NEWCODE, 0 },
256     { "note", CMD_NOTE, 0 },
257     { "o", CMD_O, 0 },
258     { "oldcode", CMD_OLDCODE, 0 },
259     { "omit", CMD_OMIT, 0 },
260     { "omitvalue", CMD_OMITVALUE, 0 },
261     { "overload", CMD_OVERLOAD, 0 },
262     { "part", CMD_PART, 0 },
263     { "printline", CMD_PRINTLINE, 0 },
264     { "printto", CMD_PRINTTO, 0 },
265     { "printuntil", CMD_PRINTUNTIL, 0 },
266     { "quotation", CMD_QUOTATION, 0 },
267     { "quotefile", CMD_QUOTEFILE, 0 },
268     { "quotefromfile", CMD_QUOTEFROMFILE, 0 },
269     { "quotefunction", CMD_QUOTEFUNCTION, 0 },
270     { "raw", CMD_RAW, 0 },
271     { "row", CMD_ROW, 0 },
272     { "sa", CMD_SA, 0 },
273     { "section1", CMD_SECTION1, 0 },
274     { "section2", CMD_SECTION2, 0 },
275     { "section3", CMD_SECTION3, 0 },
276     { "section4", CMD_SECTION4, 0 },
277     { "sidebar", CMD_SIDEBAR, 0 },
278     { "sincelist", CMD_SINCELIST, 0 },
279     { "skipline", CMD_SKIPLINE, 0 },
280     { "skipto", CMD_SKIPTO, 0 },
281     { "skipuntil", CMD_SKIPUNTIL, 0 },
282     { "snippet", CMD_SNIPPET, 0 },
283     { "span", CMD_SPAN, 0 },
284     { "sub", CMD_SUB, 0 },
285     { "sup", CMD_SUP, 0 },
286     { "table", CMD_TABLE, 0 },
287     { "tableofcontents", CMD_TABLEOFCONTENTS, 0 },
288     { "target", CMD_TARGET, 0 },
289     { "topicref", CMD_TOPICREF, 0 },
290     { "tt", CMD_TT, 0 },
291     { "uicontrol", CMD_UICONTROL, 0 },
292     { "underline", CMD_UNDERLINE, 0 },
293     { "unicode", CMD_UNICODE, 0 },
294     { "value", CMD_VALUE, 0 },
295     { "warning", CMD_WARNING, 0 },
296     { "qml", CMD_QML, 0 },
297     { "endqml", CMD_ENDQML, 0 },
298     { "cpp", CMD_CPP, 0 },
299     { "endcpp", CMD_ENDCPP, 0 },
300     { "qmltext", CMD_QMLTEXT, 0 },
301     { "endqmltext", CMD_ENDQMLTEXT, 0 },
302     { "cpptext", CMD_CPPTEXT, 0 },
303     { "endcpptext", CMD_ENDCPPTEXT, 0 },
304     { "js", CMD_JS, 0 },
305     { "endjs", CMD_ENDJS, 0 },
306     { 0, 0, 0 }
307 };
308
309 typedef QHash<QString, int> QHash_QString_int;
310 typedef QHash<QString, Macro> QHash_QString_Macro;
311
312 Q_GLOBAL_STATIC(QStringMap, aliasMap)
313 Q_GLOBAL_STATIC(QHash_QString_int, cmdHash)
314 Q_GLOBAL_STATIC(QHash_QString_Macro, macroHash)
315
316 class DocPrivateExtra
317 {
318 public:
319     QString             baseName;
320     Doc::Sections       granularity;
321     Doc::Sections       section; // ###
322     QList<Atom*>        tableOfContents;
323     QList<int>          tableOfContentsLevels;
324     QList<Atom*>        keywords;
325     QList<Atom*>        targets;
326     QStringMultiMap     metaMap;
327
328     DocPrivateExtra()
329         : granularity(Doc::Part) { }
330 };
331
332 struct Shared // ### get rid of
333 {
334     Shared()
335         : count(1) { }
336     void ref() { ++count; }
337     bool deref() { return (--count == 0); }
338
339     int count;
340 };
341
342 static QString cleanLink(const QString &link)
343 {
344     int colonPos = link.indexOf(':');
345     if ((colonPos == -1) ||
346             (!link.startsWith("file:") && !link.startsWith("mailto:")))
347         return link;
348     return link.mid(colonPos + 1).simplified();
349 }
350
351 typedef QMap<QString, ArgList> CommandMap;
352
353 class DocPrivate : public Shared
354 {
355 public:
356     DocPrivate(const Location& start = Location::null,
357                const Location& end = Location::null,
358                const QString& source = "");
359     ~DocPrivate();
360
361     void addAlso(const Text& also);
362     void constructExtra();
363     bool isEnumDocSimplifiable() const;
364
365     // ### move some of this in DocPrivateExtra
366     Location start_loc;
367     Location end_loc;
368     QString src;
369     Text text;
370     QSet<QString> params;
371     QList<Text> alsoList;
372     QStringList enumItemList;
373     QStringList omitEnumItemList;
374     QSet<QString> metacommandsUsed;
375     CommandMap metaCommandMap;
376     bool hasLegalese : 1;
377     bool hasSectioningUnits : 1;
378     DocPrivateExtra *extra;
379     TopicList topics;
380     DitaRefList ditamap_;
381 };
382
383 DocPrivate::DocPrivate(const Location& start,
384                        const Location& end,
385                        const QString& source)
386     : start_loc(start),
387       end_loc(end),
388       src(source),
389       hasLegalese(false),
390       hasSectioningUnits(false),
391       extra(0)
392 {
393     // nothing.
394 }
395
396 /*!
397   If the doc is a ditamap, the destructor deletes each element
398   in the ditamap structure. These were allocated as needed.
399  */
400 DocPrivate::~DocPrivate()
401 {
402     delete extra;
403     foreach (DitaRef* t, ditamap_) {
404         delete t;
405     }
406 }
407
408 void DocPrivate::addAlso(const Text& also)
409 {
410     alsoList.append(also);
411 }
412
413 void DocPrivate::constructExtra()
414 {
415     if (extra == 0)
416         extra = new DocPrivateExtra;
417 }
418
419 bool DocPrivate::isEnumDocSimplifiable() const
420 {
421     bool justMetColon = false;
422     int numValueTables = 0;
423
424     const Atom *atom = text.firstAtom();
425     while (atom) {
426         if (atom->type() == Atom::AutoLink || atom->type() == Atom::String) {
427             justMetColon = atom->string().endsWith(QLatin1Char(':'));
428         }
429         else if ((atom->type() == Atom::ListLeft) &&
430                  (atom->string() == ATOM_LIST_VALUE)) {
431             if (justMetColon || numValueTables > 0)
432                 return false;
433             ++numValueTables;
434         }
435         atom = atom->next();
436     }
437     return true;
438 }
439
440 class DocParser
441 {
442 public:
443     void parse(const QString &source,
444                DocPrivate *docPrivate,
445                const QSet<QString> &metaCommandSet,
446                const QSet<QString>& possibleTopics);
447
448     static int endCmdFor(int cmd);
449     static QString cmdName(int cmd);
450     static QString endCmdName(int cmd);
451     static QString untabifyEtc(const QString& str);
452     static int indentLevel(const QString& str);
453     static QString unindent(int level, const QString& str);
454     static QString slashed(const QString& str);
455
456     static int tabSize;
457     static QStringList exampleFiles;
458     static QStringList exampleDirs;
459     static QStringList sourceFiles;
460     static QStringList sourceDirs;
461     static bool quoting;
462
463 private:
464     Location& location();
465     QString detailsUnknownCommand(const QSet<QString>& metaCommandSet,
466                                   const QString& str);
467     void insertBaseName(const QString &baseName);
468     void insertTarget(const QString& target, bool keyword);
469     void include(const QString& fileName, const QString& identifier);
470     void startFormat(const QString& format, int cmd);
471     bool openCommand(int cmd);
472     bool closeCommand(int endCmd);
473     void startSection(Doc::Sections unit, int cmd);
474     void endSection(int unit, int endCmd);
475     void parseAlso();
476     void append(Atom::Type type, const QString& string = "");
477     void append(Atom::Type type, const QString& p1, const QString& p2);
478     void appendChar(QChar ch);
479     void appendWord(const QString &word);
480     void appendToCode(const QString &code);
481     void appendToCode(const QString &code, Atom::Type defaultType);
482     void startNewPara();
483     void enterPara(Atom::Type leftType = Atom::ParaLeft,
484                    Atom::Type rightType = Atom::ParaRight,
485                    const QString& string = "");
486     void leavePara();
487     void leaveValue();
488     void leaveValueList();
489     void leaveTableRow();
490     CodeMarker *quoteFromFile();
491     void expandMacro(const QString& name, const QString& def, int numParams);
492     QString expandMacroToString(const QString &name, const QString &def, int numParams);
493     Doc::Sections getSectioningUnit();
494     QString getArgument(bool verbatim = false);
495     QString getBracedArgument(bool verbatim);
496     QString getOptionalArgument();
497     QString getRestOfLine();
498     QString getMetaCommandArgument(const QString &cmdStr);
499     QString getUntilEnd(int cmd);
500     QString getCode(int cmd, CodeMarker *marker);
501     QString getUnmarkedCode(int cmd);
502
503     bool isBlankLine();
504     bool isLeftBraceAhead();
505     void skipSpacesOnLine();
506     void skipSpacesOrOneEndl();
507     void skipAllSpaces();
508     void skipToNextPreprocessorCommand();
509
510     QStack<int> openedInputs;
511
512     QString in;
513     int pos;
514     int len;
515     Location cachedLoc;
516     int cachedPos;
517
518     DocPrivate* priv;
519     enum ParagraphState {
520         OutsideParagraph,
521         InSingleLineParagraph,
522         InMultiLineParagraph
523     };
524     ParagraphState paraState;
525     bool inTableHeader;
526     bool inTableRow;
527     bool inTableItem;
528     bool indexStartedPara; // ### rename
529     Atom::Type pendingParaLeftType;
530     Atom::Type pendingParaRightType;
531     QString pendingParaString;
532
533     int braceDepth;
534     int minIndent;
535     Doc::Sections currentSection;
536     QMap<QString, Location> targetMap;
537     QMap<int, QString> pendingFormats;
538     QStack<int> openedCommands;
539     QStack<OpenedList> openedLists;
540     Quoter quoter;
541     QStack<DitaRef*> ditarefs_;
542 };
543
544 int DocParser::tabSize;
545 QStringList DocParser::exampleFiles;
546 QStringList DocParser::exampleDirs;
547 QStringList DocParser::sourceFiles;
548 QStringList DocParser::sourceDirs;
549 bool DocParser::quoting;
550
551 /*!
552   Parse the \a source string to build a Text data structure
553   in \a docPrivate. The Text data structure is a linked list
554   of Atoms.
555
556   \a metaCommandSet is the set of metacommands that may be
557   found in \a source. These metacommands are not markup text
558   commands. They are topic commands and related metacommands.
559  */
560 void DocParser::parse(const QString& source,
561                       DocPrivate *docPrivate,
562                       const QSet<QString>& metaCommandSet,
563                       const QSet<QString>& possibleTopics)
564 {
565     in = source;
566     pos = 0;
567     len = in.length();
568     cachedLoc = docPrivate->start_loc;
569     cachedPos = 0;
570     priv = docPrivate;
571     priv->text << Atom::Nop;
572     priv->topics.clear();
573
574     paraState = OutsideParagraph;
575     inTableHeader = false;
576     inTableRow = false;
577     inTableItem = false;
578     indexStartedPara = false;
579     pendingParaLeftType = Atom::Nop;
580     pendingParaRightType = Atom::Nop;
581
582     braceDepth = 0;
583     minIndent = INT_MAX;
584     currentSection = Doc::NoSection;
585     openedCommands.push(CMD_OMIT);
586     quoter.reset();
587
588     CodeMarker *marker = 0;
589     Atom *currentLinkAtom = 0;
590     QString p1, p2;
591     QStack<bool> preprocessorSkipping;
592     int numPreprocessorSkipping = 0;
593
594     while (pos < len) {
595         QChar ch = in.at(pos);
596
597         switch (ch.unicode()) {
598         case '\\':
599         {
600             QString cmdStr;
601             pos++;
602             while (pos < len) {
603                 ch = in.at(pos);
604                 if (ch.isLetterOrNumber()) {
605                     cmdStr += ch;
606                     pos++;
607                 }
608                 else {
609                     break;
610                 }
611             }
612             if (cmdStr.isEmpty()) {
613                 if (pos < len) {
614                     enterPara();
615                     if (in.at(pos).isSpace()) {
616                         skipAllSpaces();
617                         appendChar(QLatin1Char(' '));
618                     }
619                     else {
620                         appendChar(in.at(pos++));
621                     }
622                 }
623             }
624             else {
625                 int cmd = cmdHash()->value(cmdStr,NOT_A_CMD);
626                 switch (cmd) {
627                 case CMD_A:
628                     enterPara();
629                     p1 = getArgument();
630                     append(Atom::FormattingLeft,ATOM_FORMATTING_PARAMETER);
631                     append(Atom::String, p1);
632                     append(Atom::FormattingRight,ATOM_FORMATTING_PARAMETER);
633                     priv->params.insert(p1);
634                     break;
635                 case CMD_ABSTRACT:
636                     if (openCommand(cmd)) {
637                         leavePara();
638                         append(Atom::AbstractLeft);
639                     }
640                     break;
641                 case CMD_BADCODE:
642                     leavePara();
643                     append(Atom::CodeBad,getCode(CMD_BADCODE, marker));
644                     break;
645                 case CMD_BASENAME:
646                     leavePara();
647                     insertBaseName(getArgument());
648                     break;
649                 case CMD_BR:
650                     leavePara();
651                     append(Atom::BR);
652                     break;
653                 case CMD_BOLD:
654                     location().warning(tr("'\\bold' is deprecated. Use '\\b'"));
655                 case CMD_B:
656                     startFormat(ATOM_FORMATTING_BOLD, cmd);
657                     break;
658                 case CMD_BRIEF:
659                     leavePara();
660                     enterPara(Atom::BriefLeft, Atom::BriefRight);
661                     break;
662                 case CMD_C:
663                     enterPara();
664                     p1 = untabifyEtc(getArgument(true));
665                     marker = CodeMarker::markerForCode(p1);
666                     append(Atom::C, marker->markedUpCode(p1, 0, location()));
667                     break;
668                 case CMD_CAPTION:
669                     leavePara();
670                     enterPara(Atom::CaptionLeft, Atom::CaptionRight);
671                     break;
672                 case CMD_CHAPTER:
673                     startSection(Doc::Chapter, cmd);
674                     break;
675                 case CMD_CODE:
676                     leavePara();
677                     append(Atom::Code, getCode(CMD_CODE, 0));
678                     break;
679                 case CMD_QML:
680                     leavePara();
681                     append(Atom::Qml, getCode(CMD_QML, CodeMarker::markerForLanguage(QLatin1String("QML"))));
682                     break;
683                 case CMD_QMLTEXT:
684                     append(Atom::QmlText);
685                     break;
686                 case CMD_JS:
687                     leavePara();
688                     append(Atom::JavaScript, getCode(CMD_JS, CodeMarker::markerForLanguage(QLatin1String("JavaScript"))));
689                     break;
690                 case CMD_DIV:
691                     leavePara();
692                     p1 = getArgument(true);
693                     append(Atom::DivLeft, p1);
694                     openedCommands.push(cmd);
695                     break;
696                 case CMD_ENDDIV:
697                     leavePara();
698                     append(Atom::DivRight);
699                     closeCommand(cmd);
700                     break;
701                 case CMD_CODELINE:
702                 {
703                     if (!quoting) {
704                         if (priv->text.lastAtom()->type() == Atom::Code
705                                 && priv->text.lastAtom()->string().endsWith("\n\n"))
706                             priv->text.lastAtom()->chopString();
707                         appendToCode("\n");
708                     }
709                     else {
710                         append(Atom::CodeQuoteCommand, cmdStr);
711                         append(Atom::CodeQuoteArgument, " ");
712                     }
713                 }
714                     break;
715                 case CMD_DOTS:
716                 {
717                     if (!quoting) {
718                         if (priv->text.lastAtom()->type() == Atom::Code
719                                 && priv->text.lastAtom()->string().endsWith("\n\n"))
720                             priv->text.lastAtom()->chopString();
721
722                         QString arg = getOptionalArgument();
723                         int indent = 4;
724                         if (!arg.isEmpty())
725                             indent = arg.toInt();
726                         for (int i = 0; i < indent; ++i)
727                             appendToCode(" ");
728                         appendToCode("...\n");
729                     }
730                     else {
731                         append(Atom::CodeQuoteCommand, cmdStr);
732                         QString arg = getOptionalArgument();
733                         if (arg.isEmpty())
734                             arg = "4";
735                         append(Atom::CodeQuoteArgument, arg);
736                     }
737                 }
738                     break;
739                 case CMD_ELSE:
740                     if (preprocessorSkipping.size() > 0) {
741                         if (preprocessorSkipping.top()) {
742                             --numPreprocessorSkipping;
743                         }
744                         else {
745                             ++numPreprocessorSkipping;
746                         }
747                         preprocessorSkipping.top() = !preprocessorSkipping.top();
748                         (void)getRestOfLine(); // ### should ensure that it's empty
749                         if (numPreprocessorSkipping)
750                             skipToNextPreprocessorCommand();
751                     }
752                     else {
753                         location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ELSE)));
754                     }
755                     break;
756                 case CMD_ENDABSTRACT:
757                     if (closeCommand(cmd)) {
758                         leavePara();
759                         append(Atom::AbstractRight);
760                     }
761                     break;
762                 case CMD_ENDCHAPTER:
763                     endSection(Doc::Chapter, cmd);
764                     break;
765                 case CMD_ENDCODE:
766                     closeCommand(cmd);
767                     break;
768                 case CMD_ENDQML:
769                     closeCommand(cmd);
770                     break;
771                 case CMD_ENDQMLTEXT:
772                     append(Atom::EndQmlText);
773                     break;
774                 case CMD_ENDJS:
775                     closeCommand(cmd);
776                     break;
777                 case CMD_ENDFOOTNOTE:
778                     if (closeCommand(cmd)) {
779                         leavePara();
780                         append(Atom::FootnoteRight);
781                         paraState = InMultiLineParagraph; // ###
782                     }
783                     break;
784                 case CMD_ENDIF:
785                     if (preprocessorSkipping.count() > 0) {
786                         if (preprocessorSkipping.pop())
787                             --numPreprocessorSkipping;
788                         (void)getRestOfLine(); // ### should ensure that it's empty
789                         if (numPreprocessorSkipping)
790                             skipToNextPreprocessorCommand();
791                     }
792                     else {
793                         location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ENDIF)));
794                     }
795                     break;
796                 case CMD_ENDLEGALESE:
797                     if (closeCommand(cmd)) {
798                         leavePara();
799                         append(Atom::LegaleseRight);
800                     }
801                     break;
802                 case CMD_ENDLINK:
803                     if (closeCommand(cmd)) {
804                         if (priv->text.lastAtom()->type() == Atom::String
805                                 && priv->text.lastAtom()->string().endsWith(QLatin1Char(' ')))
806                             priv->text.lastAtom()->chopString();
807                         append(Atom::FormattingRight, ATOM_FORMATTING_LINK);
808                     }
809                     break;
810                 case CMD_ENDLIST:
811                     if (closeCommand(cmd)) {
812                         leavePara();
813                         if (openedLists.top().isStarted()) {
814                             append(Atom::ListItemRight,
815                                    openedLists.top().styleString());
816                             append(Atom::ListRight,
817                                    openedLists.top().styleString());
818                         }
819                         openedLists.pop();
820                     }
821                     break;
822                 case CMD_ENDMAPREF:
823                 case CMD_ENDTOPICREF:
824                     if (closeCommand(cmd)) {
825                         ditarefs_.pop(); // zzz
826                     }
827                     break;
828                 case CMD_ENDOMIT:
829                     closeCommand(cmd);
830                     break;
831                 case CMD_ENDPART:
832                     endSection(Doc::Part, cmd);
833                     break;
834                 case CMD_ENDQUOTATION:
835                     if (closeCommand(cmd)) {
836                         leavePara();
837                         append(Atom::QuotationRight);
838                     }
839                     break;
840                 case CMD_ENDRAW:
841                     location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ENDRAW)));
842                     break;
843                 case CMD_ENDSECTION1:
844                     endSection(Doc::Section1, cmd);
845                     break;
846                 case CMD_ENDSECTION2:
847                     endSection(Doc::Section2, cmd);
848                     break;
849                 case CMD_ENDSECTION3:
850                     endSection(Doc::Section3, cmd);
851                     break;
852                 case CMD_ENDSECTION4:
853                     endSection(Doc::Section4, cmd);
854                     break;
855                 case CMD_ENDSIDEBAR:
856                     if (closeCommand(cmd)) {
857                         leavePara();
858                         append(Atom::SidebarRight);
859                     }
860                     break;
861                 case CMD_ENDTABLE:
862                     if (closeCommand(cmd)) {
863                         leaveTableRow();
864                         append(Atom::TableRight);
865                     }
866                     break;
867                 case CMD_FOOTNOTE:
868                     if (openCommand(cmd)) {
869                         enterPara();
870                         append(Atom::FootnoteLeft);
871                         paraState = OutsideParagraph; // ###
872                     }
873                     break;
874                 case CMD_ANNOTATEDLIST:
875                     append(Atom::AnnotatedList, getArgument());
876                     break;
877                 case CMD_SINCELIST:
878                     append(Atom::SinceList, getRestOfLine().simplified());
879                     break;
880                 case CMD_GENERATELIST:
881                     append(Atom::GeneratedList, getArgument());
882                     break;
883                 case CMD_GRANULARITY:
884                     priv->constructExtra();
885                     priv->extra->granularity = getSectioningUnit();
886                     break;
887                 case CMD_HEADER:
888                     if (openedCommands.top() == CMD_TABLE) {
889                         leaveTableRow();
890                         append(Atom::TableHeaderLeft);
891                         inTableHeader = true;
892                     }
893                     else {
894                         if (openedCommands.contains(CMD_TABLE)) {
895                             location().warning(tr("Cannot use '\\%1' within '\\%2'")
896                                                .arg(cmdName(CMD_HEADER))
897                                                .arg(cmdName(openedCommands.top())));
898                         }
899                         else {
900                             location().warning(tr("Cannot use '\\%1' outside of '\\%2'")
901                                                .arg(cmdName(CMD_HEADER))
902                                                .arg(cmdName(CMD_TABLE)));
903                         }
904                     }
905                     break;
906                 case CMD_I:
907                     location().warning(tr("'\\i' is deprecated. Use '\\e' for italic or '\\li' for list item"));
908                 case CMD_E:
909                     startFormat(ATOM_FORMATTING_ITALIC, cmd);
910                     break;
911                 case CMD_HR:
912                     leavePara();
913                     append(Atom::HR);
914                     break;
915                 case CMD_IF:
916                     preprocessorSkipping.push(!Tokenizer::isTrue(getRestOfLine()));
917                     if (preprocessorSkipping.top())
918                         ++numPreprocessorSkipping;
919                     if (numPreprocessorSkipping)
920                         skipToNextPreprocessorCommand();
921                     break;
922                 case CMD_IMAGE:
923                     leaveValueList();
924                     append(Atom::Image, getArgument());
925                     append(Atom::ImageText, getRestOfLine());
926                     break;
927                 case CMD_IMPORTANT:
928                     leavePara();
929                     enterPara(Atom::ImportantLeft, Atom::ImportantRight);
930                     break;
931                 case CMD_INCLUDE:
932                 {
933                     QString fileName = getArgument();
934                     QString identifier = getRestOfLine();
935                     include(fileName, identifier);
936                 }
937                     break;
938                 case CMD_INLINEIMAGE:
939                     enterPara();
940                     append(Atom::InlineImage, getArgument());
941                     append(Atom::ImageText, getRestOfLine());
942                     append(Atom::String, " ");
943                     break;
944                 case CMD_INDEX:
945                     if (paraState == OutsideParagraph) {
946                         enterPara();
947                         indexStartedPara = true;
948                     }
949                     else {
950                         const Atom *last = priv->text.lastAtom();
951                         if (indexStartedPara &&
952                                 (last->type() != Atom::FormattingRight ||
953                                  last->string() != ATOM_FORMATTING_INDEX))
954                             indexStartedPara = false;
955                     }
956                     startFormat(ATOM_FORMATTING_INDEX, cmd);
957                     break;
958                 case CMD_KEYWORD:
959                     insertTarget(getRestOfLine(),true);
960                     break;
961                 case CMD_L:
962                     enterPara();
963                     if (isLeftBraceAhead()) {
964                         p1 = getArgument();
965                         append(Atom::Link, p1);
966                         if (isLeftBraceAhead()) {
967                             currentLinkAtom = priv->text.lastAtom();
968                             startFormat(ATOM_FORMATTING_LINK, cmd);
969                         }
970                         else {
971                             append(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
972                             append(Atom::String, cleanLink(p1));
973                             append(Atom::FormattingRight, ATOM_FORMATTING_LINK);
974                         }
975                     }
976                     else {
977                         p1 = getArgument();
978                         append(Atom::Link, p1);
979                         append(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
980                         append(Atom::String, cleanLink(p1));
981                         append(Atom::FormattingRight, ATOM_FORMATTING_LINK);
982                     }
983                     break;
984                 case CMD_LEGALESE:
985                     leavePara();
986                     if (openCommand(cmd))
987                         append(Atom::LegaleseLeft);
988                     docPrivate->hasLegalese = true;
989                     break;
990                 case CMD_LINK:
991                     if (openCommand(cmd)) {
992                         enterPara();
993                         p1 = getArgument();
994                         append(Atom::Link, p1);
995                         append(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
996                         skipSpacesOrOneEndl();
997                     }
998                     break;
999                 case CMD_LIST:
1000                     if (openCommand(cmd)) {
1001                         leavePara();
1002                         openedLists.push(OpenedList(location(),
1003                                                     getOptionalArgument()));
1004                     }
1005                     break;
1006                 case CMD_TOPICREF:
1007                 case CMD_MAPREF:
1008                     if (openCommand(cmd)) {
1009                         DitaRef* t = 0;
1010                         if (cmd == CMD_MAPREF)
1011                             t = new MapRef();
1012                         else
1013                             t = new TopicRef();
1014                         t->setNavtitle(getArgument(true));
1015                         if (cmd == CMD_MAPREF)
1016                             t->setHref(getArgument());
1017                         else
1018                             t->setHref(getOptionalArgument());
1019                         if (ditarefs_.isEmpty())
1020                             priv->ditamap_.append(t);
1021                         else
1022                             ditarefs_.top()->appendSubref(t);
1023                         ditarefs_.push(t);
1024                     }
1025                     break;
1026                 case CMD_META:
1027                     priv->constructExtra();
1028                     p1 = getArgument();
1029                     priv->extra->metaMap.insert(p1, getArgument());
1030                     break;
1031                 case CMD_NEWCODE:
1032                     location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_NEWCODE)));
1033                     break;
1034                 case CMD_NOTE:
1035                     leavePara();
1036                     enterPara(Atom::NoteLeft, Atom::NoteRight);
1037                     break;
1038                 case CMD_O:
1039                     location().warning(tr("'\\o' is deprecated. Use '\\li'"));
1040                 case CMD_LI:
1041                     leavePara();
1042                     if (openedCommands.top() == CMD_LIST) {
1043                         if (openedLists.top().isStarted()) {
1044                             append(Atom::ListItemRight,
1045                                    openedLists.top().styleString());
1046                         }
1047                         else {
1048                             append(Atom::ListLeft,
1049                                    openedLists.top().styleString());
1050                         }
1051                         openedLists.top().next();
1052                         append(Atom::ListItemNumber,
1053                                openedLists.top().numberString());
1054                         append(Atom::ListItemLeft,
1055                                openedLists.top().styleString());
1056                         enterPara();
1057                     }
1058                     else if (openedCommands.top() == CMD_TABLE) {
1059                         p1 = "1,1";
1060                         p2.clear();
1061                         if (isLeftBraceAhead()) {
1062                             p1 = getArgument();
1063                             if (isLeftBraceAhead()) {
1064                                 p2 = getArgument();
1065                             }
1066                         }
1067
1068                         if (!inTableHeader && !inTableRow) {
1069                             location().warning(tr("Missing '\\%1' or '\\%1' before '\\%3'")
1070                                                .arg(cmdName(CMD_HEADER))
1071                                                .arg(cmdName(CMD_ROW))
1072                                                .arg(cmdName(CMD_LI)));
1073                             append(Atom::TableRowLeft);
1074                             inTableRow = true;
1075                         }
1076                         else if (inTableItem) {
1077                             append(Atom::TableItemRight);
1078                             inTableItem = false;
1079                         }
1080
1081                         append(Atom::TableItemLeft, p1, p2);
1082                         inTableItem = true;
1083                     }
1084                     else {
1085                         location().warning(tr("Command '\\%1' outside of '\\%2' and '\\%3'")
1086                                            .arg(cmdName(cmd))
1087                                            .arg(cmdName(CMD_LIST))
1088                                            .arg(cmdName(CMD_TABLE)));
1089                     }
1090                     break;
1091                 case CMD_OLDCODE:
1092                     leavePara();
1093                     append(Atom::CodeOld, getCode(CMD_OLDCODE, marker));
1094                     append(Atom::CodeNew, getCode(CMD_NEWCODE, marker));
1095                     break;
1096                 case CMD_OMIT:
1097                     getUntilEnd(cmd);
1098                     break;
1099                 case CMD_OMITVALUE:
1100                     p1 = getArgument();
1101                     if (!priv->enumItemList.contains(p1))
1102                         priv->enumItemList.append(p1);
1103                     if (!priv->omitEnumItemList.contains(p1))
1104                         priv->omitEnumItemList.append(p1);
1105                     break;
1106                 case CMD_PART:
1107                     startSection(Doc::Part, cmd);
1108                     break;
1109                 case CMD_PRINTLINE:
1110                     leavePara();
1111                     if (!quoting)
1112                         appendToCode(quoter.quoteLine(location(), cmdStr,
1113                                                       getRestOfLine()));
1114                     else {
1115                         append(Atom::CodeQuoteCommand, cmdStr);
1116                         append(Atom::CodeQuoteArgument, getRestOfLine());
1117                     }
1118                     break;
1119                 case CMD_PRINTTO:
1120                     leavePara();
1121                     if (!quoting)
1122                         appendToCode(quoter.quoteTo(location(), cmdStr,
1123                                                     getRestOfLine()));
1124                     else {
1125                         append(Atom::CodeQuoteCommand, cmdStr);
1126                         append(Atom::CodeQuoteArgument, getRestOfLine());
1127                     }
1128                     break;
1129                 case CMD_PRINTUNTIL:
1130                     leavePara();
1131                     if (!quoting)
1132                         appendToCode(quoter.quoteUntil(location(), cmdStr,
1133                                                        getRestOfLine()));
1134                     else {
1135                         append(Atom::CodeQuoteCommand, cmdStr);
1136                         append(Atom::CodeQuoteArgument, getRestOfLine());
1137                     }
1138                     break;
1139                 case CMD_QUOTATION:
1140                     if (openCommand(cmd)) {
1141                         leavePara();
1142                         append(Atom::QuotationLeft);
1143                     }
1144                     break;
1145                 case CMD_QUOTEFILE:
1146                 {
1147                     leavePara();
1148                     QString fileName = getArgument();
1149                     Doc::quoteFromFile(location(), quoter, fileName);
1150                     if (!quoting) {
1151                         append(Atom::Code,
1152                                quoter.quoteTo(location(), cmdStr, ""));
1153                         quoter.reset();
1154                     }
1155                     else {
1156                         append(Atom::CodeQuoteCommand, cmdStr);
1157                         append(Atom::CodeQuoteArgument, fileName);
1158                     }
1159                     break;
1160                 }
1161                 case CMD_QUOTEFROMFILE:
1162                     leavePara();
1163                     if (!quoting)
1164                         quoteFromFile();
1165                     else {
1166                         append(Atom::CodeQuoteCommand, cmdStr);
1167                         append(Atom::CodeQuoteArgument, getArgument());
1168                     }
1169                     break;
1170                 case CMD_QUOTEFUNCTION:
1171                     leavePara();
1172                     marker = quoteFromFile();
1173                     p1 = getRestOfLine();
1174                     if (!quoting) {
1175                         quoter.quoteTo(location(), cmdStr,
1176                                        slashed(marker->functionBeginRegExp(p1)));
1177                         append(Atom::Code,
1178                                quoter.quoteUntil(location(), cmdStr,
1179                                                  slashed(marker->functionEndRegExp(p1))));
1180                         quoter.reset();
1181                     }
1182                     else {
1183                         append(Atom::CodeQuoteCommand, cmdStr);
1184                         append(Atom::CodeQuoteArgument, slashed(marker->functionEndRegExp(p1)));
1185                     }
1186                     break;
1187                 case CMD_RAW:
1188                     leavePara();
1189                     p1 = getRestOfLine();
1190                     if (p1.isEmpty())
1191                         location().warning(tr("Missing format name after '\\%1")
1192                                            .arg(cmdName(CMD_RAW)));
1193                     append(Atom::FormatIf, p1);
1194                     append(Atom::RawString, untabifyEtc(getUntilEnd(cmd)));
1195                     append(Atom::FormatElse);
1196                     append(Atom::FormatEndif);
1197                     break;
1198                 case CMD_ROW:
1199                     if (openedCommands.top() == CMD_TABLE) {
1200                         p1.clear();
1201                         if (isLeftBraceAhead())
1202                             p1 = getArgument(true);
1203                         leaveTableRow();
1204                         append(Atom::TableRowLeft,p1);
1205                         inTableRow = true;
1206                     }
1207                     else {
1208                         if (openedCommands.contains(CMD_TABLE)) {
1209                             location().warning(tr("Cannot use '\\%1' within '\\%2'")
1210                                                .arg(cmdName(CMD_ROW))
1211                                                .arg(cmdName(openedCommands.top())));
1212                         }
1213                         else {
1214                             location().warning(tr("Cannot use '\\%1' outside of '\\%2'")
1215                                                .arg(cmdName(CMD_ROW))
1216                                                .arg(cmdName(CMD_TABLE)));
1217                         }
1218                     }
1219                     break;
1220                 case CMD_SA:
1221                     parseAlso();
1222                     break;
1223                 case CMD_SECTION1:
1224                     startSection(Doc::Section1, cmd);
1225                     break;
1226                 case CMD_SECTION2:
1227                     startSection(Doc::Section2, cmd);
1228                     break;
1229                 case CMD_SECTION3:
1230                     startSection(Doc::Section3, cmd);
1231                     break;
1232                 case CMD_SECTION4:
1233                     startSection(Doc::Section4, cmd);
1234                     break;
1235                 case CMD_SIDEBAR:
1236                     if (openCommand(cmd)) {
1237                         leavePara();
1238                         append(Atom::SidebarLeft);
1239                     }
1240                     break;
1241                 case CMD_SKIPLINE:
1242                     leavePara();
1243                     if (!quoting)
1244                         quoter.quoteLine(location(),
1245                                          cmdStr,
1246                                          getRestOfLine());
1247                     else {
1248                         append(Atom::CodeQuoteCommand, cmdStr);
1249                         append(Atom::CodeQuoteArgument, getRestOfLine());
1250                     }
1251                     break;
1252                 case CMD_SKIPTO:
1253                     leavePara();
1254                     if (!quoting)
1255                         quoter.quoteTo(location(),
1256                                        cmdStr,
1257                                        getRestOfLine());
1258                     else {
1259                         append(Atom::CodeQuoteCommand, cmdStr);
1260                         append(Atom::CodeQuoteArgument, getRestOfLine());
1261                     }
1262                     break;
1263                 case CMD_SKIPUNTIL:
1264                     leavePara();
1265                     if (!quoting)
1266                         quoter.quoteUntil(location(),
1267                                           cmdStr,
1268                                           getRestOfLine());
1269                     else {
1270                         append(Atom::CodeQuoteCommand, cmdStr);
1271                         append(Atom::CodeQuoteArgument, getRestOfLine());
1272                     }
1273                     break;
1274                 case CMD_SPAN:
1275                     p1 = ATOM_FORMATTING_SPAN + getArgument(true);
1276                     startFormat(p1, cmd);
1277                     break;
1278                 case CMD_SNIPPET:
1279                     leavePara();
1280                 {
1281                     QString snippet = getArgument();
1282                     QString identifier = getRestOfLine();
1283                     if (quoting) {
1284                         append(Atom::SnippetCommand, cmdStr);
1285                         append(Atom::SnippetLocation, snippet);
1286                         append(Atom::SnippetIdentifier, identifier);
1287                     }
1288                     else {
1289                         marker = Doc::quoteFromFile(location(),quoter,snippet);
1290                         appendToCode(quoter.quoteSnippet(location(), identifier), marker->atomType());
1291                     }
1292                 }
1293                     break;
1294                 case CMD_SUB:
1295                     startFormat(ATOM_FORMATTING_SUBSCRIPT, cmd);
1296                     break;
1297                 case CMD_SUP:
1298                     startFormat(ATOM_FORMATTING_SUPERSCRIPT, cmd);
1299                     break;
1300                 case CMD_TABLE:
1301                     //p1 = getRestOfLine();
1302                     p1 = getOptionalArgument();
1303                     p2 = getOptionalArgument();
1304                     if (openCommand(cmd)) {
1305                         leavePara();
1306                         append(Atom::TableLeft, p1, p2);
1307                         inTableHeader = false;
1308                         inTableRow = false;
1309                         inTableItem = false;
1310                     }
1311                     break;
1312                 case CMD_TABLEOFCONTENTS:
1313                     p1 = "1";
1314                     if (isLeftBraceAhead())
1315                         p1 = getArgument();
1316                     p1 += QLatin1Char(',');
1317                     p1 += QString::number((int)getSectioningUnit());
1318                     append(Atom::TableOfContents, p1);
1319                     break;
1320                 case CMD_TARGET:
1321                     insertTarget(getRestOfLine(),false);
1322                     break;
1323                 case CMD_TT:
1324                     startFormat(ATOM_FORMATTING_TELETYPE, cmd);
1325                     break;
1326                 case CMD_UICONTROL:
1327                     startFormat(ATOM_FORMATTING_UICONTROL, cmd);
1328                     break;
1329                 case CMD_UNDERLINE:
1330                     startFormat(ATOM_FORMATTING_UNDERLINE, cmd);
1331                     break;
1332                 case CMD_UNICODE:
1333                     enterPara();
1334                     p1 = getArgument();
1335                 {
1336                     bool ok;
1337                     uint unicodeChar = p1.toUInt(&ok, 0);
1338                     if (!ok ||
1339                             (unicodeChar == 0x0000) ||
1340                             (unicodeChar > 0xFFFE)) {
1341                         location().warning(tr("Invalid Unicode character '%1' specified "
1342                                               "with '%2'")
1343                                            .arg(p1, cmdName(CMD_UNICODE)));
1344                     }
1345                     else {
1346                         append(Atom::String, QChar(unicodeChar));
1347                     }
1348                 }
1349                     break;
1350                 case CMD_VALUE:
1351                     leaveValue();
1352                     if (openedLists.top().style() == OpenedList::Value) {
1353                         p1 = getArgument();
1354                         if (!priv->enumItemList.contains(p1))
1355                             priv->enumItemList.append(p1);
1356
1357                         openedLists.top().next();
1358                         append(Atom::ListTagLeft, ATOM_LIST_VALUE);
1359                         append(Atom::String, p1);
1360                         append(Atom::ListTagRight, ATOM_LIST_VALUE);
1361                         append(Atom::ListItemLeft, ATOM_LIST_VALUE);
1362
1363                         skipSpacesOrOneEndl();
1364                         if (isBlankLine())
1365                             append(Atom::Nop);
1366                     }
1367                     else {
1368                         // ### problems
1369                     }
1370                     break;
1371                 case CMD_WARNING:
1372                     leavePara();
1373                     enterPara();
1374                     append(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1375                     append(Atom::String, "Warning:");
1376                     append(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1377                     append(Atom::String, " ");
1378                     break;
1379                 case CMD_OVERLOAD:
1380                     priv->metacommandsUsed.insert(cmdStr);
1381                     p1.clear();
1382                     if (!isBlankLine())
1383                         p1 = getRestOfLine();
1384                     if (!p1.isEmpty()) {
1385                         append(Atom::ParaLeft);
1386                         append(Atom::String, "This function overloads ");
1387                         append(Atom::AutoLink,p1);
1388                         append(Atom::String, ".");
1389                         append(Atom::ParaRight);
1390                     }
1391                     else {
1392                         append(Atom::ParaLeft);
1393                         append(Atom::String,"This is an overloaded function.");
1394                         append(Atom::ParaRight);
1395                         p1 = getMetaCommandArgument(cmdStr);
1396                     }
1397                     priv->metaCommandMap[cmdStr].append(ArgLocPair(p1,location()));
1398                     break;
1399                 case NOT_A_CMD:
1400                     if (metaCommandSet.contains(cmdStr)) {
1401                         priv->metacommandsUsed.insert(cmdStr);
1402                         QString arg = getMetaCommandArgument(cmdStr);
1403                         priv->metaCommandMap[cmdStr].append(ArgLocPair(arg,location()));
1404                         if (possibleTopics.contains(cmdStr)) {
1405                             priv->topics.append(Topic(cmdStr,arg));
1406                         }
1407                     }
1408                     else if (macroHash()->contains(cmdStr)) {
1409                         const Macro &macro = macroHash()->value(cmdStr);
1410                         int numPendingFi = 0;
1411                         QStringMap::ConstIterator d;
1412                         d = macro.otherDefs.constBegin();
1413                         while (d != macro.otherDefs.constEnd()) {
1414                             append(Atom::FormatIf, d.key());
1415                             expandMacro(cmdStr, *d, macro.numParams);
1416                             ++d;
1417
1418                             if (d == macro.otherDefs.constEnd()) {
1419                                 append(Atom::FormatEndif);
1420                             }
1421                             else {
1422                                 append(Atom::FormatElse);
1423                                 numPendingFi++;
1424                             }
1425                         }
1426                         while (numPendingFi-- > 0)
1427                             append(Atom::FormatEndif);
1428
1429                         if (!macro.defaultDef.isEmpty()) {
1430                             if (!macro.otherDefs.isEmpty()) {
1431                                 macro.defaultDefLocation.warning(
1432                                             tr("Macro cannot have both "
1433                                                "format-specific and qdoc- "
1434                                                "syntax definitions"));
1435                             }
1436                             else {
1437                                 location().push(macro.defaultDefLocation.filePath());
1438                                 in.insert(pos, expandMacroToString(cmdStr, macro.defaultDef, macro.numParams));
1439                                 len = in.length();
1440                                 openedInputs.push(pos + macro.defaultDef.length());
1441                             }
1442                         }
1443                     }
1444                     else {
1445                         location().warning(
1446                                     tr("Unknown command '\\%1'").arg(cmdStr),
1447                                     detailsUnknownCommand(metaCommandSet,cmdStr));
1448                         enterPara();
1449                         append(Atom::UnknownCommand, cmdStr);
1450                     }
1451                 }
1452             }
1453         }
1454             break;
1455         case '{':
1456             enterPara();
1457             appendChar('{');
1458             braceDepth++;
1459             pos++;
1460             break;
1461         case '}':
1462         {
1463             braceDepth--;
1464             pos++;
1465
1466             QMap<int, QString>::Iterator f = pendingFormats.find(braceDepth);
1467             if (f == pendingFormats.end()) {
1468                 enterPara();
1469                 appendChar('}');
1470             }
1471             else {
1472                 append(Atom::FormattingRight, *f);
1473                 if (*f == ATOM_FORMATTING_INDEX) {
1474                     if (indexStartedPara)
1475                         skipAllSpaces();
1476                 }
1477                 else if (*f == ATOM_FORMATTING_LINK) {
1478                     // hack for C++ to support links like
1479                     // \l{QString::}{count()}
1480                     if (currentLinkAtom &&
1481                             currentLinkAtom->string().endsWith("::")) {
1482                         QString suffix = Text::subText(currentLinkAtom,
1483                                                        priv->text.lastAtom()).toString();
1484                         currentLinkAtom->appendString(suffix);
1485                     }
1486                     currentLinkAtom = 0;
1487                 }
1488                 pendingFormats.erase(f);
1489             }
1490         }
1491             break;
1492         default:
1493         {
1494             bool newWord;
1495             switch (priv->text.lastAtom()->type()) {
1496             case Atom::ParaLeft:
1497                 newWord = true;
1498                 break;
1499             default:
1500                 newWord = false;
1501             }
1502
1503             if (paraState == OutsideParagraph) {
1504                 if (ch.isSpace()) {
1505                     ++pos;
1506                     newWord = false;
1507                 }
1508                 else {
1509                     enterPara();
1510                     newWord = true;
1511                 }
1512             }
1513             else {
1514                 if (ch.isSpace()) {
1515                     ++pos;
1516                     if ((ch == '\n') &&
1517                             (paraState == InSingleLineParagraph ||
1518                              isBlankLine())) {
1519                         leavePara();
1520                         newWord = false;
1521                     }
1522                     else {
1523                         appendChar(' ');
1524                         newWord = true;
1525                     }
1526                 }
1527                 else {
1528                     newWord = true;
1529                 }
1530             }
1531
1532             if (newWord) {
1533                 int startPos = pos;
1534                 int numInternalUppercase = 0;
1535                 int numLowercase = 0;
1536                 int numStrangeSymbols = 0;
1537
1538                 while (pos < len) {
1539                     unsigned char latin1Ch = in.at(pos).toLatin1();
1540                     if (islower(latin1Ch)) {
1541                         ++numLowercase;
1542                         ++pos;
1543                     }
1544                     else if (isupper(latin1Ch)) {
1545                         if (pos > startPos)
1546                             ++numInternalUppercase;
1547                         ++pos;
1548                     }
1549                     else if (isdigit(latin1Ch)) {
1550                         if (pos > startPos) {
1551                             ++pos;
1552                         }
1553                         else {
1554                             break;
1555                         }
1556                     }
1557                     else if (latin1Ch == '_' || latin1Ch == '@') {
1558                         ++numStrangeSymbols;
1559                         ++pos;
1560                     }
1561                     else if (latin1Ch == ':' && pos < len - 1
1562                              && in.at(pos + 1) == QLatin1Char(':')) {
1563                         ++numStrangeSymbols;
1564                         pos += 2;
1565                     }
1566                     else if (latin1Ch == '(') {
1567                         if (pos > startPos) {
1568                             if (pos < len - 1 &&
1569                                     in.at(pos + 1) == QLatin1Char(')')) {
1570                                 ++numStrangeSymbols;
1571                                 pos += 2;
1572                                 break;
1573                             }
1574                             else {
1575                                 // ### handle functions with signatures
1576                                 // and function calls
1577                                 break;
1578                             }
1579                         }
1580                         else {
1581                             break;
1582                         }
1583                     }
1584                     else {
1585                         break;
1586                     }
1587                 }
1588
1589                 if (pos == startPos) {
1590                     if (!ch.isSpace()) {
1591                         appendChar(ch);
1592                         ++pos;
1593                     }
1594                 }
1595                 else {
1596                     QString word = in.mid(startPos, pos - startPos);
1597                     // is word a C++ symbol or an English word?
1598                     if ((numInternalUppercase >= 1 && numLowercase >= 2)
1599                             || numStrangeSymbols >= 1) {
1600                         append(Atom::AutoLink, word);
1601                     }
1602                     else {
1603                         appendWord(word);
1604                     }
1605                 }
1606             }
1607         }
1608         }
1609     }
1610     leaveValueList();
1611
1612     // for compatibility
1613     if (openedCommands.top() == CMD_LEGALESE) {
1614         append(Atom::LegaleseRight);
1615         openedCommands.pop();
1616     }
1617
1618     if (openedCommands.top() != CMD_OMIT) {
1619         location().warning(tr("Missing '\\%1'").arg(endCmdName(openedCommands.top())));
1620     }
1621     else if (preprocessorSkipping.count() > 0) {
1622         location().warning(tr("Missing '\\%1'").arg(cmdName(CMD_ENDIF)));
1623     }
1624
1625     if (currentSection > Doc::NoSection) {
1626         append(Atom::SectionRight, QString::number(currentSection));
1627         currentSection = Doc::NoSection;
1628     }
1629
1630     if (priv->extra && priv->extra->granularity < priv->extra->section)
1631         priv->extra->granularity = priv->extra->section;
1632     priv->text.stripFirstAtom();
1633 }
1634
1635 /*!
1636   Returns the current location.
1637  */
1638 Location &DocParser::location()
1639 {
1640     while (!openedInputs.isEmpty() && openedInputs.top() <= pos) {
1641         cachedLoc.pop();
1642         cachedPos = openedInputs.pop();
1643     }
1644     while (cachedPos < pos)
1645         cachedLoc.advance(in.at(cachedPos++));
1646     return cachedLoc;
1647 }
1648
1649 QString DocParser::detailsUnknownCommand(const QSet<QString> &metaCommandSet,
1650                                          const QString &str)
1651 {
1652     QSet<QString> commandSet = metaCommandSet;
1653     int i = 0;
1654     while (cmds[i].english != 0) {
1655         commandSet.insert(*cmds[i].alias);
1656         i++;
1657     }
1658
1659     if (aliasMap()->contains(str))
1660         return tr("The command '\\%1' was renamed '\\%2' by the configuration"
1661                   " file. Use the new name.")
1662                 .arg(str).arg((*aliasMap())[str]);
1663
1664     QString best = nearestName(str, commandSet);
1665     if (best.isEmpty())
1666         return QString();
1667     return tr("Maybe you meant '\\%1'?").arg(best);
1668 }
1669
1670 void DocParser::insertBaseName(const QString &baseName)
1671 {
1672     priv->constructExtra();
1673     if (currentSection == priv->extra->section) {
1674         priv->extra->baseName = baseName;
1675     }
1676     else {
1677         Atom *atom = priv->text.firstAtom();
1678         Atom *sectionLeft = 0;
1679
1680         int delta = currentSection - priv->extra->section;
1681
1682         while (atom != 0) {
1683             if (atom->type() == Atom::SectionLeft &&
1684                     atom->string().toInt() == delta)
1685                 sectionLeft = atom;
1686             atom = atom->next();
1687         }
1688         if (sectionLeft != 0)
1689             (void) new Atom(sectionLeft, Atom::BaseName, baseName);
1690     }
1691 }
1692
1693 void DocParser::insertTarget(const QString &target, bool keyword)
1694 {
1695     if (targetMap.contains(target)) {
1696         location().warning(tr("Duplicate target name '%1'").arg(target));
1697         targetMap[target].warning(tr("(The previous occurrence is here)"));
1698     }
1699     else {
1700         targetMap.insert(target, location());
1701         append(Atom::Target, target);
1702         priv->constructExtra();
1703         if (keyword)
1704             priv->extra->keywords.append(priv->text.lastAtom());
1705         else
1706             priv->extra->targets.append(priv->text.lastAtom());
1707     }
1708 }
1709
1710 void DocParser::include(const QString& fileName, const QString& identifier)
1711 {
1712     if (location().depth() > 16)
1713         location().fatal(tr("Too many nested '\\%1's")
1714                          .arg(cmdName(CMD_INCLUDE)));
1715
1716     QString userFriendlyFilePath;
1717     // ### use current directory?
1718     QString filePath = Config::findFile(location(),
1719                                         sourceFiles,
1720                                         sourceDirs,
1721                                         fileName,
1722                                         userFriendlyFilePath);
1723     if (filePath.isEmpty()) {
1724         location().warning(tr("Cannot find qdoc include file '%1'").arg(fileName));
1725     }
1726     else {
1727         QFile inFile(filePath);
1728         if (!inFile.open(QFile::ReadOnly)) {
1729             location().warning(tr("Cannot open qdoc include file '%1'")
1730                                .arg(userFriendlyFilePath));
1731         }
1732         else {
1733             location().push(userFriendlyFilePath);
1734
1735             QTextStream inStream(&inFile);
1736             QString includedStuff = inStream.readAll();
1737             inFile.close();
1738
1739             if (identifier.isEmpty()) {
1740                 in.insert(pos, includedStuff);
1741                 len = in.length();
1742                 openedInputs.push(pos + includedStuff.length());
1743             }
1744             else {
1745                 QStringList lineBuffer = includedStuff.split(QLatin1Char('\n'));
1746                 int i = 0;
1747                 int startLine = -1;
1748                 while (i < lineBuffer.size()) {
1749                     if (lineBuffer[i].startsWith("//!")) {
1750                         if (lineBuffer[i].contains(identifier)) {
1751                             startLine = i+1;
1752                             break;
1753                         }
1754                     }
1755                     ++i;
1756                 }
1757                 if (startLine < 0) {
1758                     location().warning(tr("Cannot find '%1' in '%2'")
1759                                        .arg(identifier)
1760                                        .arg(userFriendlyFilePath));
1761                     return;
1762
1763                 }
1764                 QString result;
1765                 i = startLine;
1766                 do {
1767                     if (lineBuffer[i].startsWith("//!")) {
1768                         if (i<lineBuffer.size()) {
1769                             if (lineBuffer[i].contains(identifier)) {
1770                                 break;
1771                             }
1772                         }
1773                     }
1774                     else
1775                         result += lineBuffer[i] + QLatin1Char('\n');
1776                     ++i;
1777                 } while (i < lineBuffer.size());
1778                 if (result.isEmpty()) {
1779                     location().warning(tr("Empty qdoc snippet '%1' in '%2'")
1780                                        .arg(identifier)
1781                                        .arg(userFriendlyFilePath));
1782                 }
1783                 else {
1784                     in.insert(pos, result);
1785                     len = in.length();
1786                     openedInputs.push(pos + result.length());
1787                 }
1788             }
1789         }
1790     }
1791 }
1792
1793 void DocParser::startFormat(const QString& format, int cmd)
1794 {
1795     enterPara();
1796
1797     QMap<int, QString>::ConstIterator f = pendingFormats.constBegin();
1798     while (f != pendingFormats.constEnd()) {
1799         if (*f == format) {
1800             location().warning(tr("Cannot nest '\\%1' commands")
1801                                .arg(cmdName(cmd)));
1802             return;
1803         }
1804         ++f;
1805     }
1806
1807     append(Atom::FormattingLeft, format);
1808
1809     if (isLeftBraceAhead()) {
1810         skipSpacesOrOneEndl();
1811         pendingFormats.insert(braceDepth, format);
1812         ++braceDepth;
1813         ++pos;
1814     }
1815     else {
1816         append(Atom::String, getArgument());
1817         append(Atom::FormattingRight, format);
1818         if (format == ATOM_FORMATTING_INDEX && indexStartedPara) {
1819             skipAllSpaces();
1820             indexStartedPara = false;
1821         }
1822     }
1823 }
1824
1825 bool DocParser::openCommand(int cmd)
1826 {
1827     int outer = openedCommands.top();
1828     bool ok = true;
1829
1830     if (cmd != CMD_LINK) {
1831         if (outer == CMD_LIST) {
1832             ok = (cmd == CMD_FOOTNOTE || cmd == CMD_LIST);
1833         }
1834         else if (outer == CMD_ABSTRACT) {
1835             ok = (cmd == CMD_LIST ||
1836                   cmd == CMD_QUOTATION ||
1837                   cmd == CMD_TABLE);
1838         }
1839         else if (outer == CMD_SIDEBAR) {
1840             ok = (cmd == CMD_LIST ||
1841                   cmd == CMD_QUOTATION ||
1842                   cmd == CMD_SIDEBAR);
1843         }
1844         else if (outer == CMD_QUOTATION) {
1845             ok = (cmd == CMD_LIST);
1846         }
1847         else if (outer == CMD_TABLE) {
1848             ok = (cmd == CMD_LIST ||
1849                   cmd == CMD_FOOTNOTE ||
1850                   cmd == CMD_QUOTATION);
1851         }
1852         else if (outer == CMD_FOOTNOTE || outer == CMD_LINK) {
1853             ok = false;
1854         }
1855         else if (outer == CMD_TOPICREF)
1856             ok = (cmd == CMD_TOPICREF || cmd == CMD_MAPREF);
1857         else if (outer == CMD_MAPREF)
1858             ok = false;
1859     }
1860
1861     if (ok) {
1862         openedCommands.push(cmd);
1863     }
1864     else {
1865         location().warning(tr("Can't use '\\%1' in '\\%2'").arg(cmdName(cmd)).arg(cmdName(outer)));
1866     }
1867     return ok;
1868 }
1869
1870 bool DocParser::closeCommand(int endCmd)
1871 {
1872     if (endCmdFor(openedCommands.top()) == endCmd && openedCommands.size() > 1) {
1873         openedCommands.pop();
1874         return true;
1875     }
1876     else {
1877         bool contains = false;
1878         QStack<int> opened2 = openedCommands;
1879         while (opened2.size() > 1) {
1880             if (endCmdFor(opened2.top()) == endCmd) {
1881                 contains = true;
1882                 break;
1883             }
1884             opened2.pop();
1885         }
1886
1887         if (contains) {
1888             while (endCmdFor(openedCommands.top()) != endCmd && openedCommands.size() > 1) {
1889                 location().warning(tr("Missing '\\%1' before '\\%2'")
1890                                    .arg(endCmdName(openedCommands.top()))
1891                                    .arg(cmdName(endCmd)));
1892                 openedCommands.pop();
1893             }
1894         }
1895         else {
1896             location().warning(tr("Unexpected '\\%1'").arg(cmdName(endCmd)));
1897         }
1898         return false;
1899     }
1900 }
1901
1902 void DocParser::startSection(Doc::Sections unit, int cmd)
1903 {
1904     leaveValueList();
1905
1906     if (currentSection == Doc::NoSection) {
1907         currentSection = (Doc::Sections) (unit);
1908         priv->constructExtra();
1909         priv->extra->section = currentSection;
1910     }
1911     else
1912         endSection(unit,cmd);
1913
1914     append(Atom::SectionLeft, QString::number(unit));
1915     priv->constructExtra();
1916     priv->extra->tableOfContents.append(priv->text.lastAtom());
1917     priv->extra->tableOfContentsLevels.append(unit);
1918     enterPara(Atom::SectionHeadingLeft,
1919               Atom::SectionHeadingRight,
1920               QString::number(unit));
1921     currentSection = unit;
1922
1923 }
1924
1925 void DocParser::endSection(int , int) // (int unit, int endCmd)
1926 {
1927     leavePara();
1928     append(Atom::SectionRight, QString::number(currentSection));
1929     currentSection = (Doc::NoSection);
1930 }
1931
1932 void DocParser::parseAlso()
1933 {
1934     leavePara();
1935     skipSpacesOnLine();
1936     while (pos < len && in[pos] != '\n') {
1937         QString target;
1938         QString str;
1939
1940         if (in[pos] == '{') {
1941             target = getArgument();
1942             skipSpacesOnLine();
1943             if (in[pos] == '{') {
1944                 str = getArgument();
1945
1946                 // hack for C++ to support links like \l{QString::}{count()}
1947                 if (target.endsWith("::"))
1948                     target += str;
1949             }
1950             else {
1951                 str = target;
1952             }
1953 #ifdef QDOC2_COMPAT
1954         }
1955         else if (in[pos] == '\\' && in.mid(pos, 5) == "\\link") {
1956             pos += 6;
1957             target = getArgument();
1958             int endPos = in.indexOf("\\endlink", pos);
1959             if (endPos != -1) {
1960                 str = in.mid(pos, endPos - pos).trimmed();
1961                 pos = endPos + 8;
1962             }
1963 #endif
1964         }
1965         else {
1966             target = getArgument();
1967             str = cleanLink(target);
1968         }
1969
1970         Text also;
1971         also << Atom(Atom::Link, target)
1972              << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1973              << str
1974              << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1975         priv->addAlso(also);
1976
1977         skipSpacesOnLine();
1978         if (pos < len && in[pos] == ',') {
1979             pos++;
1980             skipSpacesOrOneEndl();
1981         }
1982         else if (in[pos] != '\n') {
1983             location().warning(tr("Missing comma in '\\%1'").arg(cmdName(CMD_SA)));
1984         }
1985     }
1986 }
1987
1988 //static bool debug = false;
1989 #if 0
1990 if (type == Atom::DivLeft)
1991 debug = true;
1992 if (debug)
1993 qDebug() << type << string;
1994 if (type == Atom::DivRight)
1995 debug = false;
1996 #endif
1997
1998 void DocParser::append(Atom::Type type, const QString &string)
1999 {
2000     Atom::Type lastType = priv->text.lastAtom()->type();
2001     if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n")))
2002         priv->text.lastAtom()->chopString();
2003     priv->text << Atom(type, string);
2004 }
2005
2006 void DocParser::append(Atom::Type type, const QString& p1, const QString& p2)
2007 {
2008     Atom::Type lastType = priv->text.lastAtom()->type();
2009     if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n")))
2010         priv->text.lastAtom()->chopString();
2011     priv->text << Atom(type, p1, p2);
2012 }
2013
2014 void DocParser::appendChar(QChar ch)
2015 {
2016     if (priv->text.lastAtom()->type() != Atom::String)
2017         append(Atom::String);
2018     Atom *atom = priv->text.lastAtom();
2019     if (ch == QLatin1Char(' ')) {
2020         if (!atom->string().endsWith(QLatin1Char(' ')))
2021             atom->appendChar(QLatin1Char(' '));
2022     }
2023     else
2024         atom->appendChar(ch);
2025 }
2026
2027 void DocParser::appendWord(const QString &word)
2028 {
2029     if (priv->text.lastAtom()->type() != Atom::String) {
2030         append(Atom::String, word);
2031     }
2032     else
2033         priv->text.lastAtom()->appendString(word);
2034 }
2035
2036 void DocParser::appendToCode(const QString& markedCode)
2037 {
2038     Atom::Type lastType = priv->text.lastAtom()->type();
2039     if (lastType != Atom::Qml && lastType != Atom::Code && lastType != Atom::JavaScript)
2040         append(Atom::Qml);
2041     priv->text.lastAtom()->appendString(markedCode);
2042 }
2043
2044 void DocParser::appendToCode(const QString &markedCode, Atom::Type defaultType)
2045 {
2046     Atom::Type lastType = priv->text.lastAtom()->type();
2047     if (lastType != Atom::Qml && lastType != Atom::Code && lastType != Atom::JavaScript)
2048         append(defaultType, markedCode);
2049     else
2050         priv->text.lastAtom()->appendString(markedCode);
2051 }
2052
2053 void DocParser::startNewPara()
2054 {
2055     leavePara();
2056     enterPara();
2057 }
2058
2059 void DocParser::enterPara(Atom::Type leftType,
2060                           Atom::Type rightType,
2061                           const QString& string)
2062 {
2063     if (paraState == OutsideParagraph) {
2064
2065         if ((priv->text.lastAtom()->type() != Atom::ListItemLeft) &&
2066                 (priv->text.lastAtom()->type() != Atom::DivLeft)) {
2067             leaveValueList();
2068         }
2069
2070         append(leftType, string);
2071         indexStartedPara = false;
2072         pendingParaLeftType = leftType;
2073         pendingParaRightType = rightType;
2074         pendingParaString = string;
2075         if (leftType == Atom::SectionHeadingLeft) {
2076             paraState = InSingleLineParagraph;
2077         }
2078         else {
2079             paraState = InMultiLineParagraph;
2080         }
2081         skipSpacesOrOneEndl();
2082     }
2083 }
2084
2085 void DocParser::leavePara()
2086 {
2087     if (paraState != OutsideParagraph) {
2088         if (!pendingFormats.isEmpty()) {
2089             location().warning(tr("Missing '}'"));
2090             pendingFormats.clear();
2091         }
2092
2093         if (priv->text.lastAtom()->type() == pendingParaLeftType) {
2094             priv->text.stripLastAtom();
2095         }
2096         else {
2097             if (priv->text.lastAtom()->type() == Atom::String &&
2098                     priv->text.lastAtom()->string().endsWith(QLatin1Char(' '))) {
2099                 priv->text.lastAtom()->chopString();
2100             }
2101             append(pendingParaRightType, pendingParaString);
2102         }
2103         paraState = OutsideParagraph;
2104         indexStartedPara = false;
2105         pendingParaRightType = Atom::Nop;
2106         pendingParaString.clear();
2107     }
2108 }
2109
2110 void DocParser::leaveValue()
2111 {
2112     leavePara();
2113     if (openedLists.isEmpty()) {
2114         openedLists.push(OpenedList(OpenedList::Value));
2115         append(Atom::ListLeft, ATOM_LIST_VALUE);
2116     }
2117     else {
2118         if (priv->text.lastAtom()->type() == Atom::Nop)
2119             priv->text.stripLastAtom();
2120         append(Atom::ListItemRight, ATOM_LIST_VALUE);
2121     }
2122 }
2123
2124 void DocParser::leaveValueList()
2125 {
2126     leavePara();
2127     if (!openedLists.isEmpty() &&
2128             (openedLists.top().style() == OpenedList::Value)) {
2129         if (priv->text.lastAtom()->type() == Atom::Nop)
2130             priv->text.stripLastAtom();
2131         append(Atom::ListItemRight, ATOM_LIST_VALUE);
2132         append(Atom::ListRight, ATOM_LIST_VALUE);
2133         openedLists.pop();
2134     }
2135 }
2136
2137 void DocParser::leaveTableRow()
2138 {
2139     if (inTableItem) {
2140         leavePara();
2141         append(Atom::TableItemRight);
2142         inTableItem = false;
2143     }
2144     if (inTableHeader) {
2145         append(Atom::TableHeaderRight);
2146         inTableHeader = false;
2147     }
2148     if (inTableRow) {
2149         append(Atom::TableRowRight);
2150         inTableRow = false;
2151     }
2152 }
2153
2154 CodeMarker *DocParser::quoteFromFile()
2155 {
2156     return Doc::quoteFromFile(location(), quoter, getArgument());
2157 }
2158
2159 void DocParser::expandMacro(const QString &name,
2160                             const QString &def,
2161                             int numParams)
2162 {
2163     if (numParams == 0) {
2164         append(Atom::RawString, def);
2165     }
2166     else {
2167         QStringList args;
2168         QString rawString;
2169
2170         for (int i = 0; i < numParams; i++) {
2171             if (numParams == 1 || isLeftBraceAhead()) {
2172                 args << getArgument(true);
2173             }
2174             else {
2175                 location().warning(tr("Macro '\\%1' invoked with too few"
2176                                       " arguments (expected %2, got %3)")
2177                                    .arg(name).arg(numParams).arg(i));
2178                 break;
2179             }
2180         }
2181
2182         int j = 0;
2183         while (j < def.size()) {
2184             int paramNo;
2185             if (((paramNo = def[j].unicode()) >= 1) &&
2186                     (paramNo <= numParams)) {
2187                 if (!rawString.isEmpty()) {
2188                     append(Atom::RawString, rawString);
2189                     rawString.clear();
2190                 }
2191                 append(Atom::String, args[paramNo - 1]);
2192                 j += 1;
2193             }
2194             else {
2195                 rawString += def[j++];
2196             }
2197         }
2198         if (!rawString.isEmpty())
2199             append(Atom::RawString, rawString);
2200     }
2201 }
2202
2203 QString DocParser::expandMacroToString(const QString &name, const QString &def, int numParams)
2204 {
2205     if (numParams == 0) {
2206         return def;
2207     }
2208     else {
2209         QStringList args;
2210         QString rawString;
2211
2212         for (int i = 0; i < numParams; i++) {
2213             if (numParams == 1 || isLeftBraceAhead()) {
2214                 args << getArgument(true);
2215             }
2216             else {
2217                 location().warning(tr("Macro '\\%1' invoked with too few"
2218                                       " arguments (expected %2, got %3)")
2219                                    .arg(name).arg(numParams).arg(i));
2220                 break;
2221             }
2222         }
2223
2224         int j = 0;
2225         while (j < def.size()) {
2226             int paramNo;
2227             if (((paramNo = def[j].unicode()) >= 1) &&
2228                     (paramNo <= numParams)) {
2229                 rawString += args[paramNo - 1];
2230                 j += 1;
2231             }
2232             else {
2233                 rawString += def[j++];
2234             }
2235         }
2236         return rawString;
2237     }
2238 }
2239
2240 Doc::Sections DocParser::getSectioningUnit()
2241 {
2242     QString name = getOptionalArgument();
2243
2244     if (name == "part") {
2245         return Doc::Part;
2246     }
2247     else if (name == "chapter") {
2248         return Doc::Chapter;
2249     }
2250     else if (name == "section1") {
2251         return Doc::Section1;
2252     }
2253     else if (name == "section2") {
2254         return Doc::Section2;
2255     }
2256     else if (name == "section3") {
2257         return Doc::Section3;
2258     }
2259     else if (name == "section4") {
2260         return Doc::Section4;
2261     }
2262     else if (name.isEmpty()) {
2263         return Doc::NoSection;
2264     }
2265     else {
2266         location().warning(tr("Invalid section '%1'").arg(name));
2267         return Doc::NoSection;
2268     }
2269 }
2270
2271 /*!
2272   Gets an argument that is enclosed in braces and returns it
2273   without the enclosing braces. On entry, the current character
2274   is the left brace. On exit, the current character is the one
2275   that comes afterr the right brace.
2276
2277   If \a verbatim is true, extra whitespace is retained in the
2278   returned string. Otherwise, extr whitespace is removed.
2279  */
2280 QString DocParser::getBracedArgument(bool verbatim)
2281 {
2282     QString arg;
2283     int delimDepth = 0;
2284     if (pos < (int) in.length() && in[pos] == '{') {
2285         pos++;
2286         while (pos < (int) in.length() && delimDepth >= 0) {
2287             switch (in[pos].unicode()) {
2288             case '{':
2289                 delimDepth++;
2290                 arg += QLatin1Char('{');
2291                 pos++;
2292                 break;
2293             case '}':
2294                 delimDepth--;
2295                 if (delimDepth >= 0)
2296                     arg += QLatin1Char('}');
2297                 pos++;
2298                 break;
2299             case '\\':
2300                 if (verbatim) {
2301                     arg += in[pos];
2302                     pos++;
2303                 }
2304                 else {
2305                     pos++;
2306                     if (pos < (int) in.length()) {
2307                         if (in[pos].isLetterOrNumber())
2308                             break;
2309                         arg += in[pos];
2310                         if (in[pos].isSpace()) {
2311                             skipAllSpaces();
2312                         }
2313                         else {
2314                             pos++;
2315                         }
2316                     }
2317                 }
2318                 break;
2319             default:
2320                 arg += in[pos];
2321                 pos++;
2322             }
2323         }
2324         if (delimDepth > 0)
2325             location().warning(tr("Missing '}'"));
2326     }
2327     return arg;
2328 }
2329
2330 /*!
2331   Typically, an argument ends at the next white-space. However,
2332   braces can be used to group words:
2333
2334   {a few words}
2335
2336   Also, opening and closing parentheses have to match. Thus,
2337
2338   printf("%d\n", x)
2339
2340   is an argument too, although it contains spaces. Finally,
2341   trailing punctuation is not included in an argument, nor is 's.
2342 */
2343 QString DocParser::getArgument(bool verbatim)
2344 {
2345     skipSpacesOrOneEndl();
2346
2347     int delimDepth = 0;
2348     int startPos = pos;
2349     QString arg = getBracedArgument(verbatim);
2350     if (arg.isEmpty()) {
2351         while ((pos < in.length()) &&
2352                ((delimDepth > 0) || ((delimDepth == 0) && !in[pos].isSpace()))) {
2353             switch (in[pos].unicode()) {
2354             case '(':
2355             case '[':
2356             case '{':
2357                 delimDepth++;
2358                 arg += in[pos];
2359                 pos++;
2360                 break;
2361             case ')':
2362             case ']':
2363             case '}':
2364                 delimDepth--;
2365                 if (pos == startPos || delimDepth >= 0) {
2366                     arg += in[pos];
2367                     pos++;
2368                 }
2369                 break;
2370             case '\\':
2371                 if (verbatim) {
2372                     arg += in[pos];
2373                     pos++;
2374                 }
2375                 else {
2376                     pos++;
2377                     if (pos < (int) in.length()) {
2378                         if (in[pos].isLetterOrNumber())
2379                             break;
2380                         arg += in[pos];
2381                         if (in[pos].isSpace()) {
2382                             skipAllSpaces();
2383                         }
2384                         else {
2385                             pos++;
2386                         }
2387                     }
2388                 }
2389                 break;
2390             default:
2391                 arg += in[pos];
2392                 pos++;
2393             }
2394         }
2395         if ((arg.length() > 1) &&
2396                 (QString(".,:;!?").indexOf(in[pos - 1]) != -1) &&
2397                 !arg.endsWith("...")) {
2398             arg.truncate(arg.length() - 1);
2399             pos--;
2400         }
2401         if (arg.length() > 2 && in.mid(pos - 2, 2) == "'s") {
2402             arg.truncate(arg.length() - 2);
2403             pos -= 2;
2404         }
2405     }
2406     return arg.simplified();
2407 }
2408
2409 QString DocParser::getOptionalArgument()
2410 {
2411     skipSpacesOrOneEndl();
2412     if (pos + 1 < (int) in.length() && in[pos] == '\\' &&
2413             in[pos + 1].isLetterOrNumber()) {
2414         return QString();
2415     }
2416     else {
2417         return getArgument();
2418     }
2419 }
2420
2421 QString DocParser::getRestOfLine()
2422 {
2423     QString t;
2424
2425     skipSpacesOnLine();
2426
2427     bool trailingSlash = false;
2428
2429     do {
2430         int begin = pos;
2431
2432         while (pos < in.size() && in[pos] != '\n') {
2433             if (in[pos] == '\\' && !trailingSlash) {
2434                 trailingSlash = true;
2435                 ++pos;
2436                 while ((pos < in.size()) &&
2437                        in[pos].isSpace() &&
2438                        (in[pos] != '\n'))
2439                     ++pos;
2440             }
2441             else {
2442                 trailingSlash = false;
2443                 ++pos;
2444             }
2445         }
2446
2447         if (!t.isEmpty())
2448             t += QLatin1Char(' ');
2449         t += in.mid(begin, pos - begin).simplified();
2450
2451         if (trailingSlash) {
2452             t.chop(1);
2453             t = t.simplified();
2454         }
2455         if (pos < in.size())
2456             ++pos;
2457     } while (pos < in.size() && trailingSlash);
2458
2459     return t;
2460 }
2461
2462 /*!
2463   The metacommand argument is normally the remaining text to
2464   the right of the metacommand itself. The extra blanks are
2465   stripped and the argument string is returned.
2466  */
2467 QString DocParser::getMetaCommandArgument(const QString &cmdStr)
2468 {
2469     skipSpacesOnLine();
2470
2471     int begin = pos;
2472     int parenDepth = 0;
2473
2474     while (pos < in.size() && (in[pos] != '\n' || parenDepth > 0)) {
2475         if (in.at(pos) == '(')
2476             ++parenDepth;
2477         else if (in.at(pos) == ')')
2478             --parenDepth;
2479
2480         ++pos;
2481     }
2482     if (pos == in.size() && parenDepth > 0) {
2483         pos = begin;
2484         location().warning(tr("Unbalanced parentheses in '%1'").arg(cmdStr));
2485     }
2486
2487     QString t = in.mid(begin, pos - begin).simplified();
2488     skipSpacesOnLine();
2489     return t;
2490 }
2491
2492 QString DocParser::getUntilEnd(int cmd)
2493 {
2494     int endCmd = endCmdFor(cmd);
2495     QRegExp rx("\\\\" + cmdName(endCmd) + "\\b");
2496     QString t;
2497     int end = rx.indexIn(in, pos);
2498
2499     if (end == -1) {
2500         location().warning(tr("Missing '\\%1'").arg(cmdName(endCmd)));
2501         pos = in.length();
2502     }
2503     else {
2504         t = in.mid(pos, end - pos);
2505         pos = end + rx.matchedLength();
2506     }
2507     return t;
2508 }
2509
2510 QString DocParser::getCode(int cmd, CodeMarker *marker)
2511 {
2512     QString code = untabifyEtc(getUntilEnd(cmd));
2513     int indent = indentLevel(code);
2514     if (indent < minIndent)
2515         minIndent = indent;
2516     code = unindent(minIndent, code);
2517     if (!marker)
2518         marker = CodeMarker::markerForCode(code);
2519     return marker->markedUpCode(code, 0, location());
2520 }
2521
2522 /*!
2523   Was used only for generating doxygen output.
2524  */
2525 QString DocParser::getUnmarkedCode(int cmd)
2526 {
2527     QString code = getUntilEnd(cmd);
2528     return code;
2529 }
2530
2531 bool DocParser::isBlankLine()
2532 {
2533     int i = pos;
2534
2535     while (i < len && in[i].isSpace()) {
2536         if (in[i] == '\n')
2537             return true;
2538         i++;
2539     }
2540     return false;
2541 }
2542
2543 bool DocParser::isLeftBraceAhead()
2544 {
2545     int numEndl = 0;
2546     int i = pos;
2547
2548     while (i < len && in[i].isSpace() && numEndl < 2) {
2549         // ### bug with '\\'
2550         if (in[i] == '\n')
2551             numEndl++;
2552         i++;
2553     }
2554     return numEndl < 2 && i < len && in[i] == '{';
2555 }
2556
2557 /*!
2558   Skips to the next non-space character or EOL.
2559  */
2560 void DocParser::skipSpacesOnLine()
2561 {
2562     while ((pos < in.length()) &&
2563            in[pos].isSpace() &&
2564            (in[pos].unicode() != '\n'))
2565         ++pos;
2566 }
2567
2568 /*!
2569   Skips spaces and on EOL.
2570  */
2571 void DocParser::skipSpacesOrOneEndl()
2572 {
2573     int firstEndl = -1;
2574     while (pos < (int) in.length() && in[pos].isSpace()) {
2575         QChar ch = in[pos];
2576         if (ch == '\n') {
2577             if (firstEndl == -1) {
2578                 firstEndl = pos;
2579             }
2580             else {
2581                 pos = firstEndl;
2582                 break;
2583             }
2584         }
2585         pos++;
2586     }
2587 }
2588
2589 void DocParser::skipAllSpaces()
2590 {
2591     while (pos < len && in[pos].isSpace())
2592         pos++;
2593 }
2594
2595 void DocParser::skipToNextPreprocessorCommand()
2596 {
2597     QRegExp rx("\\\\(?:" + cmdName(CMD_IF) + QLatin1Char('|') +
2598                cmdName(CMD_ELSE) + QLatin1Char('|') +
2599                cmdName(CMD_ENDIF) + ")\\b");
2600     int end = rx.indexIn(in, pos + 1); // ### + 1 necessary?
2601
2602     if (end == -1)
2603         pos = in.length();
2604     else
2605         pos = end;
2606 }
2607
2608 int DocParser::endCmdFor(int cmd)
2609 {
2610     switch (cmd) {
2611     case CMD_ABSTRACT:
2612         return CMD_ENDABSTRACT;
2613     case CMD_BADCODE:
2614         return CMD_ENDCODE;
2615     case CMD_CHAPTER:
2616         return CMD_ENDCHAPTER;
2617     case CMD_CODE:
2618         return CMD_ENDCODE;
2619     case CMD_DIV:
2620         return CMD_ENDDIV;
2621     case CMD_QML:
2622         return CMD_ENDQML;
2623     case CMD_QMLTEXT:
2624         return CMD_ENDQMLTEXT;
2625     case CMD_JS:
2626         return CMD_ENDJS;
2627     case CMD_FOOTNOTE:
2628         return CMD_ENDFOOTNOTE;
2629     case CMD_LEGALESE:
2630         return CMD_ENDLEGALESE;
2631     case CMD_LINK:
2632         return CMD_ENDLINK;
2633     case CMD_LIST:
2634         return CMD_ENDLIST;
2635     case CMD_NEWCODE:
2636         return CMD_ENDCODE;
2637     case CMD_OLDCODE:
2638         return CMD_NEWCODE;
2639     case CMD_OMIT:
2640         return CMD_ENDOMIT;
2641     case CMD_PART:
2642         return CMD_ENDPART;
2643     case CMD_QUOTATION:
2644         return CMD_ENDQUOTATION;
2645     case CMD_RAW:
2646         return CMD_ENDRAW;
2647     case CMD_SECTION1:
2648         return CMD_ENDSECTION1;
2649     case CMD_SECTION2:
2650         return CMD_ENDSECTION2;
2651     case CMD_SECTION3:
2652         return CMD_ENDSECTION3;
2653     case CMD_SECTION4:
2654         return CMD_ENDSECTION4;
2655     case CMD_SIDEBAR:
2656         return CMD_ENDSIDEBAR;
2657     case CMD_TABLE:
2658         return CMD_ENDTABLE;
2659     case CMD_TOPICREF:
2660         return CMD_ENDTOPICREF;
2661     case CMD_MAPREF:
2662         return CMD_ENDMAPREF;
2663     default:
2664         return cmd;
2665     }
2666 }
2667
2668 QString DocParser::cmdName(int cmd)
2669 {
2670     return *cmds[cmd].alias;
2671 }
2672
2673 QString DocParser::endCmdName(int cmd)
2674 {
2675     return cmdName(endCmdFor(cmd));
2676 }
2677
2678 QString DocParser::untabifyEtc(const QString& str)
2679 {
2680     QString result;
2681     result.reserve(str.length());
2682     int column = 0;
2683
2684     for (int i = 0; i < str.length(); i++) {
2685         const QChar c = str.at(i);
2686         if (c == QLatin1Char('\r'))
2687             continue;
2688         if (c == QLatin1Char('\t')) {
2689             result += "        " + (column % tabSize);
2690             column = ((column / tabSize) + 1) * tabSize;
2691             continue;
2692         }
2693         if (c == QLatin1Char('\n')) {
2694             while (result.endsWith(QLatin1Char(' ')))
2695                 result.chop(1);
2696             result += c;
2697             column = 0;
2698             continue;
2699         }
2700         result += c;
2701         column++;
2702     }
2703
2704     while (result.endsWith("\n\n"))
2705         result.truncate(result.length() - 1);
2706     while (result.startsWith(QLatin1Char('\n')))
2707         result = result.mid(1);
2708
2709     return result;
2710 }
2711
2712 int DocParser::indentLevel(const QString& str)
2713 {
2714     int minIndent = INT_MAX;
2715     int column = 0;
2716
2717     for (int i = 0; i < (int) str.length(); i++) {
2718         if (str[i] == '\n') {
2719             column = 0;
2720         }
2721         else {
2722             if (str[i] != ' ' && column < minIndent)
2723                 minIndent = column;
2724             column++;
2725         }
2726     }
2727     return minIndent;
2728 }
2729
2730 QString DocParser::unindent(int level, const QString& str)
2731 {
2732     if (level == 0)
2733         return str;
2734
2735     QString t;
2736     int column = 0;
2737
2738     for (int i = 0; i < (int) str.length(); i++) {
2739         if (str[i] == QLatin1Char('\n')) {
2740             t += '\n';
2741             column = 0;
2742         }
2743         else {
2744             if (column >= level)
2745                 t += str[i];
2746             column++;
2747         }
2748     }
2749     return t;
2750 }
2751
2752 QString DocParser::slashed(const QString& str)
2753 {
2754     QString result = str;
2755     result.replace(QLatin1Char('/'), "\\/");
2756     return QLatin1Char('/') + result + QLatin1Char('/');
2757 }
2758
2759 #define COMMAND_BRIEF                   Doc::alias("brief")
2760 #define COMMAND_QMLBRIEF                Doc::alias("qmlbrief")
2761
2762 Doc::Doc(const Location& start_loc,
2763          const Location& end_loc,
2764          const QString& source,
2765          const QSet<QString>& metaCommandSet)
2766 {
2767     priv = new DocPrivate(start_loc,end_loc,source);
2768     DocParser parser;
2769     parser.parse(source,priv,metaCommandSet,QSet<QString>());
2770 }
2771
2772 /*!
2773   Parse the qdoc comment \a source. Build up a list of all the topic
2774   commands found including their arguments.  This constructor is used
2775   when there can be more than one topic command in theqdoc comment.
2776   Normally, there is only one topic command in a qdoc comment, but in
2777   QML documentation, there is the case where the qdoc \e{qmlproperty}
2778   command can appear multiple times in a qdoc comment.
2779  */
2780 Doc::Doc(const Location& start_loc,
2781          const Location& end_loc,
2782          const QString& source,
2783          const QSet<QString>& metaCommandSet,
2784          const QSet<QString>& topics)
2785 {
2786     priv = new DocPrivate(start_loc,end_loc,source);
2787     DocParser parser;
2788     parser.parse(source,priv,metaCommandSet,topics);
2789 }
2790
2791 Doc::Doc(const Doc& doc)
2792     : priv(0)
2793 {
2794     operator=(doc);
2795 }
2796
2797 Doc::~Doc()
2798 {
2799     if (priv && priv->deref())
2800         delete priv;
2801 }
2802
2803 Doc &Doc::operator=(const Doc& doc)
2804 {
2805     if (doc.priv)
2806         doc.priv->ref();
2807     if (priv && priv->deref())
2808         delete priv;
2809     priv = doc.priv;
2810     return *this;
2811 }
2812
2813 void Doc::renameParameters(const QStringList &oldNames,
2814                            const QStringList &newNames)
2815 {
2816     if (priv && oldNames != newNames) {
2817         detach();
2818
2819         priv->params = newNames.toSet();
2820
2821         Atom *atom = priv->text.firstAtom();
2822         while (atom) {
2823             if (atom->type() == Atom::FormattingLeft
2824                     && atom->string() == ATOM_FORMATTING_PARAMETER) {
2825                 atom = atom->next();
2826                 if (!atom)
2827                     return;
2828                 int index = oldNames.indexOf(atom->string());
2829                 if (index != -1 && index < newNames.count())
2830                     atom->setString(newNames.at(index));
2831             }
2832             atom = atom->next();
2833         }
2834     }
2835 }
2836
2837 void Doc::simplifyEnumDoc()
2838 {
2839     if (priv) {
2840         if (priv->isEnumDocSimplifiable()) {
2841             detach();
2842
2843             Text newText;
2844
2845             Atom *atom = priv->text.firstAtom();
2846             while (atom) {
2847                 if ((atom->type() == Atom::ListLeft) &&
2848                         (atom->string() == ATOM_LIST_VALUE)) {
2849                     while (atom && ((atom->type() != Atom::ListRight) ||
2850                                     (atom->string() != ATOM_LIST_VALUE)))
2851                         atom = atom->next();
2852                     if (atom)
2853                         atom = atom->next();
2854                 }
2855                 else {
2856                     newText << *atom;
2857                     atom = atom->next();
2858                 }
2859             }
2860             priv->text = newText;
2861         }
2862     }
2863 }
2864
2865 void Doc::setBody(const Text &text)
2866 {
2867     detach();
2868     priv->text = text;
2869 }
2870
2871 /*!
2872   Returns the starting location of a qdoc comment.
2873  */
2874 const Location &Doc::location() const
2875 {
2876     static const Location dummy;
2877     return priv == 0 ? dummy : priv->start_loc;
2878 }
2879
2880 /*!
2881   Returns the starting location of a qdoc comment.
2882  */
2883 const Location& Doc::startLocation() const
2884 {
2885     return location();
2886 }
2887
2888 /*!
2889   Returns the ending location of a qdoc comment.
2890  */
2891 const Location& Doc::endLocation() const
2892 {
2893     static const Location dummy;
2894     return priv == 0 ? dummy : priv->end_loc;
2895 }
2896
2897 const QString &Doc::source() const
2898 {
2899     static QString null;
2900     return priv == 0 ? null : priv->src;
2901 }
2902
2903 bool Doc::isEmpty() const
2904 {
2905     return priv == 0 || priv->src.isEmpty();
2906 }
2907
2908 const Text& Doc::body() const
2909 {
2910     static const Text dummy;
2911     return priv == 0 ? dummy : priv->text;
2912 }
2913
2914 Text Doc::briefText(bool inclusive) const
2915 {
2916     return body().subText(Atom::BriefLeft, Atom::BriefRight, 0, inclusive);
2917 }
2918
2919 Text Doc::trimmedBriefText(const QString &className) const
2920 {
2921     QString classNameOnly = className;
2922     if (className.contains("::"))
2923         classNameOnly = className.split("::").last();
2924
2925     Text originalText = briefText();
2926     Text resultText;
2927     const Atom *atom = originalText.firstAtom();
2928     if (atom) {
2929         QString briefStr;
2930         QString whats;
2931         bool standardWording = true;
2932
2933         /*
2934           This code is really ugly. The entire \brief business
2935           should be rethought.
2936         */
2937         while (atom) {
2938             if (atom->type() == Atom::AutoLink || atom->type() == Atom::String) {
2939                 briefStr += atom->string();
2940             }
2941             atom = atom->next();
2942         }
2943
2944         QStringList w = briefStr.split(QLatin1Char(' '));
2945         if (!w.isEmpty() && w.first() == "Returns") {
2946         }
2947         else {
2948             if (!w.isEmpty() && w.first() == "The")
2949                 w.removeFirst();
2950             else {
2951                 location().warning(
2952                             tr("Nonstandard wording in '\\%1' text for '%2' (expected 'The')")
2953                             .arg(COMMAND_BRIEF).arg(className));
2954                 standardWording = false;
2955             }
2956
2957             if (!w.isEmpty() && (w.first() == className || w.first() == classNameOnly))
2958                 w.removeFirst();
2959             else {
2960                 location().warning(
2961                             tr("Nonstandard wording in '\\%1' text for '%2' (expected '%3')")
2962                             .arg(COMMAND_BRIEF).arg(className).arg(className));
2963                 standardWording = false;
2964             }
2965
2966             if (!w.isEmpty() && ((w.first() == "class") ||
2967                                  (w.first() == "function") ||
2968                                  (w.first() == "macro") ||
2969                                  (w.first() == "widget") ||
2970                                  (w.first() == "namespace") ||
2971                                  (w.first() == "header")))
2972                 w.removeFirst();
2973             else {
2974                 location().warning(
2975                             tr("Nonstandard wording in '\\%1' text for '%2' ("
2976                                "expected 'class', 'function', 'macro', 'widget', "
2977                                "'namespace' or 'header')")
2978                             .arg(COMMAND_BRIEF).arg(className));
2979                 standardWording = false;
2980             }
2981
2982             if (!w.isEmpty() && (w.first() == "is" || w.first() == "provides"))
2983                 w.removeFirst();
2984
2985             if (!w.isEmpty() && (w.first() == "a" || w.first() == "an"))
2986                 w.removeFirst();
2987         }
2988
2989         whats = w.join(" ");
2990
2991         if (whats.endsWith(QLatin1Char('.')))
2992             whats.truncate(whats.length() - 1);
2993
2994         if (whats.isEmpty()) {
2995             location().warning(
2996                         tr("Nonstandard wording in '\\%1' text for '%2' (expected more text)")
2997                         .arg(COMMAND_BRIEF).arg(className));
2998             standardWording = false;
2999         }
3000         else
3001             whats[0] = whats[0].toUpper();
3002
3003         // ### move this once \brief is abolished for properties
3004         if (standardWording)
3005             resultText << whats;
3006     }
3007     return resultText;
3008 }
3009
3010 Text Doc::legaleseText() const
3011 {
3012     if (priv == 0 || !priv->hasLegalese)
3013         return Text();
3014     else
3015         return body().subText(Atom::LegaleseLeft, Atom::LegaleseRight);
3016 }
3017
3018 const QString& Doc::baseName() const
3019 {
3020     static QString null;
3021     if (priv == 0 || priv->extra == 0) {
3022         return null;
3023     }
3024     else {
3025         return priv->extra->baseName;
3026     }
3027 }
3028
3029 Doc::Sections Doc::granularity() const
3030 {
3031     if (priv == 0 || priv->extra == 0) {
3032         return DocPrivateExtra().granularity;
3033     }
3034     else {
3035         return priv->extra->granularity;
3036     }
3037 }
3038
3039 const QSet<QString> &Doc::parameterNames() const
3040 {
3041     return priv == 0 ? *null_Set_QString() : priv->params;
3042 }
3043
3044 const QStringList &Doc::enumItemNames() const
3045 {
3046     return priv == 0 ? *null_QStringList() : priv->enumItemList;
3047 }
3048
3049 const QStringList &Doc::omitEnumItemNames() const
3050 {
3051     return priv == 0 ? *null_QStringList() : priv->omitEnumItemList;
3052 }
3053
3054 const QSet<QString> &Doc::metaCommandsUsed() const
3055 {
3056     return priv == 0 ? *null_Set_QString() : priv->metacommandsUsed;
3057 }
3058
3059 /*!
3060   Returns a reference to the list of topic commands used in the
3061   current qdoc comment. Normally there is only one, but there
3062   can be multiple \e{qmlproperty} commands, for example.
3063  */
3064 const TopicList& Doc::topicsUsed() const
3065 {
3066     return priv == 0 ? *nullTopicList() : priv->topics;
3067 }
3068
3069 ArgList Doc::metaCommandArgs(const QString& metacommand) const
3070 {
3071     return priv == 0 ? ArgList() : priv->metaCommandMap.value(metacommand);
3072 }
3073
3074 const QList<Text> &Doc::alsoList() const
3075 {
3076     return priv == 0 ? *null_QList_Text() : priv->alsoList;
3077 }
3078
3079 bool Doc::hasTableOfContents() const
3080 {
3081     return priv && priv->extra && !priv->extra->tableOfContents.isEmpty();
3082 }
3083
3084 bool Doc::hasKeywords() const
3085 {
3086     return priv && priv->extra && !priv->extra->keywords.isEmpty();
3087 }
3088
3089 bool Doc::hasTargets() const
3090 {
3091     return priv && priv->extra && !priv->extra->targets.isEmpty();
3092 }
3093
3094 const QList<Atom *> &Doc::tableOfContents() const
3095 {
3096     priv->constructExtra();
3097     return priv->extra->tableOfContents;
3098 }
3099
3100 const QList<int> &Doc::tableOfContentsLevels() const
3101 {
3102     priv->constructExtra();
3103     return priv->extra->tableOfContentsLevels;
3104 }
3105
3106 const QList<Atom *> &Doc::keywords() const
3107 {
3108     priv->constructExtra();
3109     return priv->extra->keywords;
3110 }
3111
3112 const QList<Atom *> &Doc::targets() const
3113 {
3114     priv->constructExtra();
3115     return priv->extra->targets;
3116 }
3117
3118 const QStringMultiMap &Doc::metaTagMap() const
3119 {
3120     return priv && priv->extra ? priv->extra->metaMap : *null_QStringMultiMap();
3121 }
3122
3123 void Doc::initialize(const Config& config)
3124 {
3125     DocParser::tabSize = config.getInt(CONFIG_TABSIZE);
3126     DocParser::exampleFiles = config.getCleanPathList(CONFIG_EXAMPLES);
3127     DocParser::exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
3128     DocParser::sourceFiles = config.getCleanPathList(CONFIG_SOURCES);
3129     DocParser::sourceDirs = config.getCleanPathList(CONFIG_SOURCEDIRS);
3130     DocParser::quoting = config.getBool(CONFIG_QUOTINGINFORMATION);
3131
3132     QmlClassNode::qmlOnly = config.getBool(CONFIG_QMLONLY);
3133
3134     QStringMap reverseAliasMap;
3135
3136     QSet<QString> commands = config.subVars(CONFIG_ALIAS);
3137     QSet<QString>::ConstIterator c = commands.constBegin();
3138     while (c != commands.constEnd()) {
3139         QString alias = config.getString(CONFIG_ALIAS + Config::dot + *c);
3140         if (reverseAliasMap.contains(alias)) {
3141             config.lastLocation().warning(tr("Command name '\\%1' cannot stand"
3142                                              " for both '\\%2' and '\\%3'")
3143                                           .arg(alias)
3144                                           .arg(reverseAliasMap[alias])
3145                                           .arg(*c));
3146         }
3147         else {
3148             reverseAliasMap.insert(alias, *c);
3149         }
3150         aliasMap()->insert(*c, alias);
3151         ++c;
3152     }
3153
3154     int i = 0;
3155     while (cmds[i].english) {
3156         cmds[i].alias = new QString(alias(cmds[i].english));
3157         cmdHash()->insert(*cmds[i].alias, cmds[i].no);
3158
3159         if (cmds[i].no != i)
3160             Location::internalError(tr("command %1 missing").arg(i));
3161         i++;
3162     }
3163
3164     QSet<QString> macroNames = config.subVars(CONFIG_MACRO);
3165     QSet<QString>::ConstIterator n = macroNames.constBegin();
3166     while (n != macroNames.constEnd()) {
3167         QString macroDotName = CONFIG_MACRO + Config::dot + *n;
3168         Macro macro;
3169         macro.numParams = -1;
3170         macro.defaultDef = config.getString(macroDotName);
3171         if (!macro.defaultDef.isEmpty()) {
3172             macro.defaultDefLocation = config.lastLocation();
3173             macro.numParams = Config::numParams(macro.defaultDef);
3174         }
3175         bool silent = false;
3176
3177         QSet<QString> formats = config.subVars(macroDotName);
3178         QSet<QString>::ConstIterator f = formats.constBegin();
3179         while (f != formats.constEnd()) {
3180             QString def = config.getString(macroDotName + Config::dot + *f);
3181             if (!def.isEmpty()) {
3182                 macro.otherDefs.insert(*f, def);
3183                 int m = Config::numParams(def);
3184                 if (macro.numParams == -1) {
3185                     macro.numParams = m;
3186                 }
3187                 else if (macro.numParams != m) {
3188                     if (!silent) {
3189                         QString other = tr("default");
3190                         if (macro.defaultDef.isEmpty())
3191                             other = macro.otherDefs.constBegin().key();
3192                         config.lastLocation().warning(tr("Macro '\\%1' takes"
3193                                                          " inconsistent number"
3194                                                          " of arguments (%2"
3195                                                          " %3, %4 %5)")
3196                                                       .arg(*n)
3197                                                       .arg(*f)
3198                                                       .arg(m)
3199                                                       .arg(other)
3200                                                       .arg(macro.numParams));
3201                         silent = true;
3202                     }
3203                     if (macro.numParams < m)
3204                         macro.numParams = m;
3205                 }
3206             }
3207             ++f;
3208         }
3209
3210         if (macro.numParams != -1)
3211             macroHash()->insert(*n, macro);
3212         ++n;
3213     }
3214 }
3215
3216 void Doc::terminate()
3217 {
3218     DocParser::exampleFiles.clear();
3219     DocParser::exampleDirs.clear();
3220     DocParser::sourceFiles.clear();
3221     DocParser::sourceDirs.clear();
3222     aliasMap()->clear();
3223     cmdHash()->clear();
3224     macroHash()->clear();
3225
3226     int i = 0;
3227     while (cmds[i].english) {
3228         delete cmds[i].alias;
3229         cmds[i].alias = 0;
3230         ++i;
3231     }
3232 }
3233
3234 QString Doc::alias(const QString &english)
3235 {
3236     return aliasMap()->value(english, english);
3237 }
3238
3239 /*!
3240   Trims the deadwood out of \a str. i.e., this function
3241   cleans up \a str.
3242  */
3243 void Doc::trimCStyleComment(Location& location, QString& str)
3244 {
3245     QString cleaned;
3246     Location m = location;
3247     bool metAsterColumn = true;
3248     int asterColumn = location.columnNo() + 1;
3249     int i;
3250
3251     for (i = 0; i < (int) str.length(); i++) {
3252         if (m.columnNo() == asterColumn) {
3253             if (str[i] != '*')
3254                 break;
3255             cleaned += ' ';
3256             metAsterColumn = true;
3257         }
3258         else {
3259             if (str[i] == '\n') {
3260                 if (!metAsterColumn)
3261                     break;
3262                 metAsterColumn = false;
3263             }
3264             cleaned += str[i];
3265         }
3266         m.advance(str[i]);
3267     }
3268     if (cleaned.length() == str.length())
3269         str = cleaned;
3270
3271     for (int i = 0; i < 3; i++)
3272         location.advance(str[i]);
3273     str = str.mid(3, str.length() - 5);
3274 }
3275
3276 CodeMarker *Doc::quoteFromFile(const Location &location,
3277                                Quoter &quoter,
3278                                const QString &fileName)
3279 {
3280     quoter.reset();
3281
3282     QString code;
3283
3284     QString userFriendlyFilePath;
3285     QString filePath = Config::findFile(location,
3286                                         DocParser::exampleFiles,
3287                                         DocParser::exampleDirs,
3288                                         fileName, userFriendlyFilePath);
3289     if (filePath.isEmpty()) {
3290         location.warning(tr("Cannot find file to quote from: '%1'").arg(fileName));
3291     }
3292     else {
3293         QFile inFile(filePath);
3294         if (!inFile.open(QFile::ReadOnly)) {
3295             location.warning(tr("Cannot open file to quote from: '%1'").arg(userFriendlyFilePath));
3296         }
3297         else {
3298             QTextStream inStream(&inFile);
3299             code = DocParser::untabifyEtc(inStream.readAll());
3300         }
3301     }
3302
3303     QString dirPath = QFileInfo(filePath).path();
3304     CodeMarker *marker = CodeMarker::markerForFileName(fileName);
3305     quoter.quoteFromFile(userFriendlyFilePath,
3306                          code,
3307                          marker->markedUpCode(code, 0, location));
3308     return marker;
3309 }
3310
3311 QString Doc::canonicalTitle(const QString &title)
3312 {
3313     // The code below is equivalent to the following chunk, but _much_
3314     // faster (accounts for ~10% of total running time)
3315     //
3316     //  QRegExp attributeExpr("[^A-Za-z0-9]+");
3317     //  QString result = title.toLower();
3318     //  result.replace(attributeExpr, " ");
3319     //  result = result.simplified();
3320     //  result.replace(QLatin1Char(' '), QLatin1Char('-'));
3321
3322     QString result;
3323     result.reserve(title.size());
3324
3325     bool dashAppended = false;
3326     bool begun = false;
3327     int lastAlnum = 0;
3328     for (int i = 0; i != title.size(); ++i) {
3329         uint c = title.at(i).unicode();
3330         if (c >= 'A' && c <= 'Z')
3331             c -= 'A' - 'a';
3332         bool alnum = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
3333         if (alnum) {
3334             result += QLatin1Char(c);
3335             begun = true;
3336             dashAppended = false;
3337             lastAlnum = result.size();
3338         }
3339         else if (!dashAppended) {
3340             if (begun)
3341                 result += QLatin1Char('-');
3342             dashAppended = true;
3343         }
3344     }
3345     result.truncate(lastAlnum);
3346     return result;
3347 }
3348
3349 void Doc::detach()
3350 {
3351     if (!priv) {
3352         priv = new DocPrivate;
3353         return;
3354     }
3355     if (priv->count == 1)
3356         return;
3357
3358     --priv->count;
3359
3360     DocPrivate *newPriv = new DocPrivate(*priv);
3361     newPriv->count = 1;
3362     if (priv->extra)
3363         newPriv->extra = new DocPrivateExtra(*priv->extra);
3364
3365     priv = newPriv;
3366 }
3367
3368 /*!
3369   The destructor deletes all the sub-TopicRefs.
3370  */
3371 TopicRef::~TopicRef()
3372 {
3373     foreach (DitaRef* t, subrefs_) {
3374         delete t;
3375     }
3376 }
3377
3378 /*!
3379   Returns a reference to the structure that will be used
3380   for generating a DITA mao.
3381  */
3382 const DitaRefList& Doc::ditamap() const { return priv->ditamap_; }
3383
3384 QT_END_NAMESPACE