Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
docparser.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3#include "docparser.h"
4
5#include "codemarker.h"
6#include "doc.h"
7#include "docprivate.h"
8#include "editdistance.h"
9#include "macro.h"
10#include "openedlist.h"
11#include "tokenizer.h"
12
13#include <QtCore/qfile.h>
14#include <QtCore/qregularexpression.h>
15#include <QtCore/qtextstream.h>
16
17#include <cctype>
18#include <climits>
19#include <functional>
20
22
23using namespace Qt::StringLiterals;
24
25DocUtilities &DocParser::s_utilities = DocUtilities::instance();
26
27enum {
128
129static struct
130{
131 const char *name;
132 int no;
133 bool is_formatting_command { false };
134} cmds[] = { { "a", CMD_A, true },
135 { "annotatedlist", CMD_ANNOTATEDLIST },
136 { "b", CMD_B, true },
137 { "badcode", CMD_BADCODE },
138 { "bold", CMD_BOLD, true },
139 { "br", CMD_BR },
140 { "brief", CMD_BRIEF },
141 { "c", CMD_C, true },
142 { "caption", CMD_CAPTION },
143 { "code", CMD_CODE },
144 { "codeline", CMD_CODELINE },
145 { "compareswith", CMD_COMPARESWITH },
146 { "details", CMD_DETAILS },
147 { "div", CMD_DIV },
148 { "dots", CMD_DOTS },
149 { "e", CMD_E, true },
150 { "else", CMD_ELSE },
151 { "endcode", CMD_ENDCODE },
152 { "endcompareswith", CMD_ENDCOMPARESWITH },
153 { "enddetails", CMD_ENDDETAILS },
154 { "enddiv", CMD_ENDDIV },
155 { "endfootnote", CMD_ENDFOOTNOTE },
156 { "endif", CMD_ENDIF },
157 { "endlegalese", CMD_ENDLEGALESE },
158 { "endlink", CMD_ENDLINK },
159 { "endlist", CMD_ENDLIST },
160 { "endmapref", CMD_ENDMAPREF },
161 { "endomit", CMD_ENDOMIT },
162 { "endquotation", CMD_ENDQUOTATION },
163 { "endraw", CMD_ENDRAW },
164 { "endsection1", CMD_ENDSECTION1 }, // ### don't document for now
165 { "endsection2", CMD_ENDSECTION2 }, // ### don't document for now
166 { "endsection3", CMD_ENDSECTION3 }, // ### don't document for now
167 { "endsection4", CMD_ENDSECTION4 }, // ### don't document for now
168 { "endsidebar", CMD_ENDSIDEBAR },
169 { "endtable", CMD_ENDTABLE },
170 { "footnote", CMD_FOOTNOTE },
171 { "generatelist", CMD_GENERATELIST },
172 { "header", CMD_HEADER },
173 { "hr", CMD_HR },
174 { "i", CMD_I, true },
175 { "if", CMD_IF },
176 { "image", CMD_IMAGE },
177 { "important", CMD_IMPORTANT },
178 { "include", CMD_INCLUDE },
179 { "inlineimage", CMD_INLINEIMAGE },
180 { "index", CMD_INDEX }, // ### don't document for now
181 { "input", CMD_INPUT },
182 { "keyword", CMD_KEYWORD },
183 { "l", CMD_L },
184 { "legalese", CMD_LEGALESE },
185 { "li", CMD_LI },
186 { "link", CMD_LINK },
187 { "list", CMD_LIST },
188 { "meta", CMD_META },
189 { "note", CMD_NOTE },
190 { "o", CMD_O },
191 { "omit", CMD_OMIT },
192 { "omitvalue", CMD_OMITVALUE },
193 { "overload", CMD_OVERLOAD },
194 { "printline", CMD_PRINTLINE },
195 { "printto", CMD_PRINTTO },
196 { "printuntil", CMD_PRINTUNTIL },
197 { "quotation", CMD_QUOTATION },
198 { "quotefile", CMD_QUOTEFILE },
199 { "quotefromfile", CMD_QUOTEFROMFILE },
200 { "raw", CMD_RAW },
201 { "row", CMD_ROW },
202 { "sa", CMD_SA },
203 { "section1", CMD_SECTION1 },
204 { "section2", CMD_SECTION2 },
205 { "section3", CMD_SECTION3 },
206 { "section4", CMD_SECTION4 },
207 { "sidebar", CMD_SIDEBAR },
208 { "sincelist", CMD_SINCELIST },
209 { "skipline", CMD_SKIPLINE },
210 { "skipto", CMD_SKIPTO },
211 { "skipuntil", CMD_SKIPUNTIL },
212 { "snippet", CMD_SNIPPET },
213 { "span", CMD_SPAN },
214 { "sub", CMD_SUB, true },
215 { "sup", CMD_SUP, true },
216 { "table", CMD_TABLE },
217 { "tableofcontents", CMD_TABLEOFCONTENTS },
218 { "target", CMD_TARGET },
219 { "tm", CMD_TM, true },
220 { "tt", CMD_TT, true },
221 { "uicontrol", CMD_UICONTROL, true },
222 { "underline", CMD_UNDERLINE, true },
223 { "unicode", CMD_UNICODE },
224 { "value", CMD_VALUE },
225 { "warning", CMD_WARNING },
226 { "qml", CMD_QML },
227 { "endqml", CMD_ENDQML },
228 { "cpp", CMD_CPP },
229 { "endcpp", CMD_ENDCPP },
230 { "cpptext", CMD_CPPTEXT },
231 { "endcpptext", CMD_ENDCPPTEXT },
232 { nullptr, 0 } };
233
236bool DocParser::s_quoting = false;
237FileResolver *DocParser::file_resolver{ nullptr };
238static void processComparesWithCommand(DocPrivate *priv, const Location &location);
239
240static QString cleanLink(const QString &link)
241{
242 qsizetype colonPos = link.indexOf(':');
243 if ((colonPos == -1) || (!link.startsWith("file:") && !link.startsWith("mailto:")))
244 return link;
245 return link.mid(colonPos + 1).simplified();
246}
247
248void DocParser::initialize(const Config &config, FileResolver &file_resolver)
249{
250 s_tabSize = config.get(CONFIG_TABSIZE).asInt();
251 s_ignoreWords = config.get(CONFIG_IGNOREWORDS).asStringList();
252
253 int i = 0;
254 while (cmds[i].name) {
255 s_utilities.cmdHash.insert(cmds[i].name, cmds[i].no);
256
257 if (cmds[i].no != i)
258 Location::internalError(QStringLiteral("command %1 missing").arg(i));
259 ++i;
260 }
261
262 // If any of the formats define quotinginformation, activate quoting
263 DocParser::s_quoting = config.get(CONFIG_QUOTINGINFORMATION).asBool();
264 const auto &outputFormats = config.getOutputFormats();
265 for (const auto &format : outputFormats)
266 DocParser::s_quoting = DocParser::s_quoting
267 || config.get(format + Config::dot + CONFIG_QUOTINGINFORMATION).asBool();
268
269 // KLUDGE: file_resolver is temporarily a pointer. See the
270 // comment for file_resolver in the header file for more context.
271 DocParser::file_resolver = &file_resolver;
272}
273
274/*!
275 Parse the \a source string to build a Text data structure
276 in \a docPrivate. The Text data structure is a linked list
277 of Atoms.
278
279 \a metaCommandSet is the set of metacommands that may be
280 found in \a source. These metacommands are not markup text
281 commands. They are topic commands and related metacommands.
282 */
283void DocParser::parse(const QString &source, DocPrivate *docPrivate,
284 const QSet<QString> &metaCommandSet, const QSet<QString> &possibleTopics)
285{
286 m_input = source;
287 m_position = 0;
288 m_inputLength = m_input.size();
289 m_cachedLocation = docPrivate->m_start_loc;
290 m_cachedPosition = 0;
291 m_private = docPrivate;
292 m_private->m_text << Atom::Nop;
293 m_private->m_topics.clear();
294
295 m_paragraphState = OutsideParagraph;
296 m_inTableHeader = false;
297 m_inTableRow = false;
298 m_inTableItem = false;
299 m_indexStartedParagraph = false;
300 m_pendingParagraphLeftType = Atom::Nop;
301 m_pendingParagraphRightType = Atom::Nop;
302
303 m_braceDepth = 0;
304 m_currentSection = Doc::NoSection;
305 m_openedCommands.push(CMD_OMIT);
306 m_quoter.reset();
307
308 CodeMarker *marker = nullptr;
309 Atom *currentLinkAtom = nullptr;
310 QString p1, p2;
311 QStack<bool> preprocessorSkipping;
312 int numPreprocessorSkipping = 0;
313
314 while (m_position < m_inputLength) {
315 QChar ch = m_input.at(m_position);
316
317 switch (ch.unicode()) {
318 case '\\': {
319 QString cmdStr;
320 m_backslashPosition = m_position;
321 ++m_position;
322 while (m_position < m_inputLength) {
323 ch = m_input.at(m_position);
324 if (ch.isLetterOrNumber()) {
325 cmdStr += ch;
326 ++m_position;
327 } else {
328 break;
329 }
330 }
331 m_endPosition = m_position;
332 if (cmdStr.isEmpty()) {
333 if (m_position < m_inputLength) {
334 enterPara();
335 if (m_input.at(m_position).isSpace()) {
336 skipAllSpaces();
337 appendChar(QLatin1Char(' '));
338 } else {
339 appendChar(m_input.at(m_position++));
340 }
341 }
342 } else {
343 // Ignore quoting atoms to make appendToCode()
344 // append to the correct atom.
345 if (!s_quoting || !isQuote(m_private->m_text.lastAtom()))
346 m_lastAtom = m_private->m_text.lastAtom();
347
348 int cmd = s_utilities.cmdHash.value(cmdStr, NOT_A_CMD);
349 switch (cmd) {
350 case CMD_A:
351 enterPara();
352 p1 = getArgument();
354 appendAtom(Atom(Atom::String, p1));
356 m_private->m_params.insert(p1);
357 break;
358 case CMD_BADCODE:
359 leavePara();
360 appendAtom(Atom(Atom::CodeBad,
361 getCode(CMD_BADCODE, marker, getMetaCommandArgument(cmdStr))));
362 break;
363 case CMD_BR:
364 enterPara();
365 appendAtom(Atom(Atom::BR));
366 break;
367 case CMD_BOLD:
368 location().warning(QStringLiteral("'\\bold' is deprecated. Use '\\b'"));
369 Q_FALLTHROUGH();
370 case CMD_B:
371 startFormat(ATOM_FORMATTING_BOLD, cmd);
372 break;
373 case CMD_BRIEF:
374 leavePara();
375 enterPara(Atom::BriefLeft, Atom::BriefRight);
376 break;
377 case CMD_C:
378 enterPara();
379 p1 = untabifyEtc(getArgument(ArgumentParsingOptions::Verbatim));
380 marker = CodeMarker::markerForCode(p1);
381 appendAtom(Atom(Atom::C, marker->markedUpCode(p1, nullptr, location())));
382 break;
383 case CMD_CAPTION:
384 leavePara();
385 enterPara(Atom::CaptionLeft, Atom::CaptionRight);
386 break;
387 case CMD_CODE:
388 leavePara();
389 appendAtom(Atom(Atom::Code, getCode(CMD_CODE, nullptr, getMetaCommandArgument(cmdStr))));
390 break;
391 case CMD_QML:
392 leavePara();
393 appendAtom(Atom(Atom::Qml,
394 getCode(CMD_QML, CodeMarker::markerForLanguage(QLatin1String("QML")),
395 getMetaCommandArgument(cmdStr))));
396 break;
397 case CMD_DETAILS:
398 leavePara();
399 appendAtom(Atom(Atom::DetailsLeft, getArgument()));
400 m_openedCommands.push(cmd);
401 break;
402 case CMD_ENDDETAILS:
403 leavePara();
404 appendAtom(Atom(Atom::DetailsRight));
405 closeCommand(cmd);
406 break;
407 case CMD_DIV:
408 leavePara();
409 p1 = getArgument(ArgumentParsingOptions::Verbatim);
410 appendAtom(Atom(Atom::DivLeft, p1));
411 m_openedCommands.push(cmd);
412 break;
413 case CMD_ENDDIV:
414 leavePara();
415 appendAtom(Atom(Atom::DivRight));
416 closeCommand(cmd);
417 break;
418 case CMD_CODELINE:
419 if (s_quoting) {
420 appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
421 appendAtom(Atom(Atom::CodeQuoteArgument, " "));
422 }
423 if (isCode(m_lastAtom) && m_lastAtom->string().endsWith("\n\n"))
424 m_lastAtom->chopString();
425 appendToCode("\n");
426 break;
427 case CMD_DOTS: {
428 QString arg = getOptionalArgument();
429 if (arg.isEmpty())
430 arg = "4";
431 if (s_quoting) {
432 appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
433 appendAtom(Atom(Atom::CodeQuoteArgument, arg));
434 }
435 if (isCode(m_lastAtom) && m_lastAtom->string().endsWith("\n\n"))
436 m_lastAtom->chopString();
437
438 int indent = arg.toInt();
439 for (int i = 0; i < indent; ++i)
440 appendToCode(" ");
441 appendToCode("...\n");
442 break;
443 }
444 case CMD_ELSE:
445 if (!preprocessorSkipping.empty()) {
446 if (preprocessorSkipping.top()) {
447 --numPreprocessorSkipping;
448 } else {
449 ++numPreprocessorSkipping;
450 }
451 preprocessorSkipping.top() = !preprocessorSkipping.top();
452 (void)getRestOfLine(); // ### should ensure that it's empty
453 if (numPreprocessorSkipping)
454 skipToNextPreprocessorCommand();
455 } else {
456 location().warning(
457 QStringLiteral("Unexpected '\\%1'").arg(cmdName(CMD_ELSE)));
458 }
459 break;
460 case CMD_ENDCODE:
461 closeCommand(cmd);
462 break;
463 case CMD_ENDQML:
464 closeCommand(cmd);
465 break;
466 case CMD_ENDFOOTNOTE:
467 if (closeCommand(cmd)) {
468 leavePara();
469 appendAtom(Atom(Atom::FootnoteRight));
470 }
471 break;
472 case CMD_ENDIF:
473 if (preprocessorSkipping.size() > 0) {
474 if (preprocessorSkipping.pop())
475 --numPreprocessorSkipping;
476 (void)getRestOfLine(); // ### should ensure that it's empty
477 if (numPreprocessorSkipping)
478 skipToNextPreprocessorCommand();
479 } else {
480 location().warning(
481 QStringLiteral("Unexpected '\\%1'").arg(cmdName(CMD_ENDIF)));
482 }
483 break;
484 case CMD_ENDLEGALESE:
485 if (closeCommand(cmd)) {
486 leavePara();
487 appendAtom(Atom(Atom::LegaleseRight));
488 }
489 break;
490 case CMD_ENDLINK:
491 if (closeCommand(cmd)) {
493 && m_private->m_text.lastAtom()->string().endsWith(QLatin1Char(' ')))
496 }
497 break;
498 case CMD_ENDLIST:
499 if (closeCommand(cmd)) {
500 leavePara();
501 if (m_openedLists.top().isStarted()) {
502 appendAtom(Atom(Atom::ListItemRight, m_openedLists.top().styleString()));
503 appendAtom(Atom(Atom::ListRight, m_openedLists.top().styleString()));
504 }
505 m_openedLists.pop();
506 }
507 break;
508 case CMD_ENDOMIT:
509 closeCommand(cmd);
510 break;
511 case CMD_ENDQUOTATION:
512 if (closeCommand(cmd)) {
513 leavePara();
514 appendAtom(Atom(Atom::QuotationRight));
515 }
516 break;
517 case CMD_ENDRAW:
518 location().warning(
519 QStringLiteral("Unexpected '\\%1'").arg(cmdName(CMD_ENDRAW)));
520 break;
521 case CMD_ENDSECTION1:
522 endSection(Doc::Section1, cmd);
523 break;
524 case CMD_ENDSECTION2:
525 endSection(Doc::Section2, cmd);
526 break;
527 case CMD_ENDSECTION3:
528 endSection(Doc::Section3, cmd);
529 break;
530 case CMD_ENDSECTION4:
531 endSection(Doc::Section4, cmd);
532 break;
533 case CMD_ENDSIDEBAR:
534 if (closeCommand(cmd)) {
535 leavePara();
536 appendAtom(Atom(Atom::SidebarRight));
537 }
538 break;
539 case CMD_ENDTABLE:
540 if (closeCommand(cmd)) {
541 leaveTableRow();
542 appendAtom(Atom(Atom::TableRight));
543 }
544 break;
545 case CMD_FOOTNOTE:
546 if (openCommand(cmd)) {
547 enterPara();
548 appendAtom(Atom(Atom::FootnoteLeft));
549 }
550 break;
551 case CMD_ANNOTATEDLIST: {
552 // Optional sorting directive [ascending|descending]
553 if (isLeftBracketAhead())
554 p2 = getBracketedArgument();
555 else
556 p2.clear();
557 appendAtom(Atom(Atom::AnnotatedList, getArgument(), p2));
558 } break;
559 case CMD_SINCELIST:
560 leavePara();
561 appendAtom(Atom(Atom::SinceList, getRestOfLine().simplified()));
562 break;
563 case CMD_GENERATELIST: {
564 // Optional sorting directive [ascending|descending]
565 if (isLeftBracketAhead())
566 p2 = getBracketedArgument();
567 else
568 p2.clear();
569 QString arg1 = getArgument();
570 QString arg2 = getOptionalArgument();
571 if (!arg2.isEmpty())
572 arg1 += " " + arg2;
573 appendAtom(Atom(Atom::GeneratedList, arg1, p2));
574 } break;
575 case CMD_HEADER:
576 if (m_openedCommands.top() == CMD_TABLE) {
577 leaveTableRow();
578 appendAtom(Atom(Atom::TableHeaderLeft));
579 m_inTableHeader = true;
580 } else {
581 if (m_openedCommands.contains(CMD_TABLE))
582 location().warning(QStringLiteral("Cannot use '\\%1' within '\\%2'")
583 .arg(cmdName(CMD_HEADER),
584 cmdName(m_openedCommands.top())));
585 else
586 location().warning(
587 QStringLiteral("Cannot use '\\%1' outside of '\\%2'")
588 .arg(cmdName(CMD_HEADER), cmdName(CMD_TABLE)));
589 }
590 break;
591 case CMD_I:
592 location().warning(QStringLiteral(
593 "'\\i' is deprecated. Use '\\e' for italic or '\\li' for list item"));
594 Q_FALLTHROUGH();
595 case CMD_E:
596 startFormat(ATOM_FORMATTING_ITALIC, cmd);
597 break;
598 case CMD_HR:
599 leavePara();
600 appendAtom(Atom(Atom::HR));
601 break;
602 case CMD_IF:
603 preprocessorSkipping.push(!Tokenizer::isTrue(getRestOfLine()));
604 if (preprocessorSkipping.top())
605 ++numPreprocessorSkipping;
606 if (numPreprocessorSkipping)
607 skipToNextPreprocessorCommand();
608 break;
609 case CMD_IMAGE:
610 leaveValueList();
611 appendAtom(Atom(Atom::Image, getArgument()));
612 appendAtom(Atom(Atom::ImageText, getRestOfLine()));
613 break;
614 case CMD_IMPORTANT:
615 leavePara();
616 enterPara(Atom::ImportantLeft, Atom::ImportantRight);
617 break;
618 case CMD_INCLUDE:
619 case CMD_INPUT: {
620 QString fileName = getArgument();
621 QStringList parameters;
622 QString identifier;
623 if (isLeftBraceAhead()) {
624 identifier = getArgument();
625 while (isLeftBraceAhead() && parameters.size() < 9)
626 parameters << getArgument();
627 } else {
628 identifier = getRestOfLine();
629 }
630 include(fileName, identifier, parameters);
631 break;
632 }
633 case CMD_INLINEIMAGE:
634 enterPara();
635 appendAtom(Atom(Atom::InlineImage, getArgument()));
636 //Append ImageText only if the following
637 //argument is enclosed in braces.
638 if (isLeftBraceAhead()) {
639 appendAtom(Atom(Atom::ImageText, getArgument()));
640 appendAtom(Atom(Atom::String, " "));
641 }
642 break;
643 case CMD_INDEX:
644 if (m_paragraphState == OutsideParagraph) {
645 enterPara();
646 m_indexStartedParagraph = true;
647 } else {
648 const Atom *last = m_private->m_text.lastAtom();
649 if (m_indexStartedParagraph
651 || last->string() != ATOM_FORMATTING_INDEX))
652 m_indexStartedParagraph = false;
653 }
654 startFormat(ATOM_FORMATTING_INDEX, cmd);
655 break;
656 case CMD_KEYWORD:
657 leavePara();
658 insertKeyword(getRestOfLine());
659 break;
660 case CMD_L:
661 enterPara();
662 if (isLeftBracketAhead())
663 p2 = getBracketedArgument();
664
665 p1 = getArgument();
666
667 appendAtom(LinkAtom(p1, p2, location()));
668
669 if (isLeftBraceAhead()) {
670 currentLinkAtom = m_private->m_text.lastAtom();
671 startFormat(ATOM_FORMATTING_LINK, cmd);
672 } else {
674 appendAtom(Atom(Atom::String, cleanLink(p1)));
676 }
677
678 p2.clear();
679
680 break;
681 case CMD_LEGALESE:
682 leavePara();
683 if (openCommand(cmd))
684 appendAtom(Atom(Atom::LegaleseLeft));
685 docPrivate->m_hasLegalese = true;
686 break;
687 case CMD_LINK:
688 if (openCommand(cmd)) {
689 enterPara();
690 p1 = getArgument();
691 appendAtom(Atom(Atom::Link, p1));
693 skipSpacesOrOneEndl();
694 }
695 break;
696 case CMD_LIST:
697 if (openCommand(cmd)) {
698 leavePara();
699 m_openedLists.push(OpenedList(location(), getOptionalArgument()));
700 }
701 break;
702 case CMD_META:
703 m_private->constructExtra();
704 p1 = getArgument();
705 m_private->extra->m_metaMap.insert(p1, getArgument());
706 break;
707 case CMD_NOTE:
708 leavePara();
709 enterPara(Atom::NoteLeft, Atom::NoteRight);
710 break;
711 case CMD_O:
712 location().warning(QStringLiteral("'\\o' is deprecated. Use '\\li'"));
713 Q_FALLTHROUGH();
714 case CMD_LI:
715 leavePara();
716 if (m_openedCommands.top() == CMD_LIST) {
717 if (m_openedLists.top().isStarted())
718 appendAtom(Atom(Atom::ListItemRight, m_openedLists.top().styleString()));
719 else
720 appendAtom(Atom(Atom::ListLeft, m_openedLists.top().styleString()));
721 m_openedLists.top().next();
722 appendAtom(Atom(Atom::ListItemNumber, m_openedLists.top().numberString()));
723 appendAtom(Atom(Atom::ListItemLeft, m_openedLists.top().styleString()));
724 enterPara();
725 } else if (m_openedCommands.top() == CMD_TABLE) {
726 p1 = "1,1";
727 p2.clear();
728 if (isLeftBraceAhead()) {
729 p1 = getArgument();
730 if (isLeftBraceAhead())
731 p2 = getArgument();
732 }
733
734 if (!m_inTableHeader && !m_inTableRow) {
735 location().warning(
736 QStringLiteral("Missing '\\%1' or '\\%2' before '\\%3'")
737 .arg(cmdName(CMD_HEADER), cmdName(CMD_ROW),
738 cmdName(CMD_LI)));
739 appendAtom(Atom(Atom::TableRowLeft));
740 m_inTableRow = true;
741 } else if (m_inTableItem) {
742 appendAtom(Atom(Atom::TableItemRight));
743 m_inTableItem = false;
744 }
745
746 appendAtom(Atom(Atom::TableItemLeft, p1, p2));
747 m_inTableItem = true;
748 } else
749 location().warning(
750 QStringLiteral("Command '\\%1' outside of '\\%2' and '\\%3'")
751 .arg(cmdName(cmd), cmdName(CMD_LIST), cmdName(CMD_TABLE)));
752 break;
753 case CMD_OMIT:
754 getUntilEnd(cmd);
755 break;
756 case CMD_OMITVALUE: {
757 leavePara();
758 p1 = getArgument();
759 if (!m_private->m_enumItemList.contains(p1))
760 m_private->m_enumItemList.append(p1);
761 if (!m_private->m_omitEnumItemList.contains(p1))
762 m_private->m_omitEnumItemList.append(p1);
763 skipSpacesOrOneEndl();
764 // Skip potential description paragraph
765 while (m_position < m_inputLength && !isBlankLine()) {
766 skipAllSpaces();
767 if (qsizetype pos = m_position; pos < m_input.size()
768 && m_input.at(pos++).unicode() == '\\') {
769 QString nextCmdStr;
770 while (pos < m_input.size() && m_input[pos].isLetterOrNumber())
771 nextCmdStr += m_input[pos++];
772 int nextCmd = s_utilities.cmdHash.value(cmdStr, NOT_A_CMD);
773 if (nextCmd == cmd || nextCmd == CMD_VALUE)
774 break;
775 }
776 getRestOfLine();
777 }
778 break;
779 }
780 case CMD_COMPARESWITH:
781 leavePara();
782 p1 = getRestOfLine();
783 if (openCommand(cmd))
784 appendAtom(Atom(Atom::ComparesLeft, p1));
785 break;
787 if (closeCommand(cmd)) {
788 leavePara();
789 appendAtom(Atom(Atom::ComparesRight));
790 processComparesWithCommand(m_private, location());
791 }
792 break;
793 case CMD_PRINTLINE: {
794 leavePara();
795 QString rest = getRestOfLine();
796 if (s_quoting) {
797 appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
798 appendAtom(Atom(Atom::CodeQuoteArgument, rest));
799 }
800 appendToCode(m_quoter.quoteLine(location(), cmdStr, rest));
801 break;
802 }
803 case CMD_PRINTTO: {
804 leavePara();
805 QString rest = getRestOfLine();
806 if (s_quoting) {
807 appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
808 appendAtom(Atom(Atom::CodeQuoteArgument, rest));
809 }
810 appendToCode(m_quoter.quoteTo(location(), cmdStr, rest));
811 break;
812 }
813 case CMD_PRINTUNTIL: {
814 leavePara();
815 QString rest = getRestOfLine();
816 if (s_quoting) {
817 appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
818 appendAtom(Atom(Atom::CodeQuoteArgument, rest));
819 }
820 appendToCode(m_quoter.quoteUntil(location(), cmdStr, rest));
821 break;
822 }
823 case CMD_QUOTATION:
824 if (openCommand(cmd)) {
825 leavePara();
826 appendAtom(Atom(Atom::QuotationLeft));
827 }
828 break;
829 case CMD_QUOTEFILE: {
830 leavePara();
831
832 QString fileName = getArgument();
833 quoteFromFile(fileName);
834 if (s_quoting) {
835 appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
836 appendAtom(Atom(Atom::CodeQuoteArgument, fileName));
837 }
838 appendAtom(Atom(Atom::Code, m_quoter.quoteTo(location(), cmdStr, QString())));
839 m_quoter.reset();
840 break;
841 }
842 case CMD_QUOTEFROMFILE: {
843 leavePara();
844 QString arg = getArgument();
845 if (s_quoting) {
846 appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
847 appendAtom(Atom(Atom::CodeQuoteArgument, arg));
848 }
849 quoteFromFile(arg);
850 break;
851 }
852 case CMD_RAW:
853 leavePara();
854 p1 = getRestOfLine();
855 if (p1.isEmpty())
856 location().warning(QStringLiteral("Missing format name after '\\%1'")
857 .arg(cmdName(CMD_RAW)));
858 appendAtom(Atom(Atom::FormatIf, p1));
859 appendAtom(Atom(Atom::RawString, untabifyEtc(getUntilEnd(cmd))));
860 appendAtom(Atom(Atom::FormatElse));
861 appendAtom(Atom(Atom::FormatEndif));
862 break;
863 case CMD_ROW:
864 if (m_openedCommands.top() == CMD_TABLE) {
865 p1.clear();
866 if (isLeftBraceAhead())
867 p1 = getArgument(ArgumentParsingOptions::Verbatim);
868 leaveTableRow();
869 appendAtom(Atom(Atom::TableRowLeft, p1));
870 m_inTableRow = true;
871 } else {
872 if (m_openedCommands.contains(CMD_TABLE))
873 location().warning(QStringLiteral("Cannot use '\\%1' within '\\%2'")
874 .arg(cmdName(CMD_ROW),
875 cmdName(m_openedCommands.top())));
876 else
877 location().warning(QStringLiteral("Cannot use '\\%1' outside of '\\%2'")
878 .arg(cmdName(CMD_ROW), cmdName(CMD_TABLE)));
879 }
880 break;
881 case CMD_SA:
882 parseAlso();
883 break;
884 case CMD_SECTION1:
885 startSection(Doc::Section1, cmd);
886 break;
887 case CMD_SECTION2:
888 startSection(Doc::Section2, cmd);
889 break;
890 case CMD_SECTION3:
891 startSection(Doc::Section3, cmd);
892 break;
893 case CMD_SECTION4:
894 startSection(Doc::Section4, cmd);
895 break;
896 case CMD_SIDEBAR:
897 if (openCommand(cmd)) {
898 leavePara();
899 appendAtom(Atom(Atom::SidebarLeft));
900 }
901 break;
902 case CMD_SKIPLINE: {
903 leavePara();
904 QString rest = getRestOfLine();
905 if (s_quoting) {
906 appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
907 appendAtom(Atom(Atom::CodeQuoteArgument, rest));
908 }
909 m_quoter.quoteLine(location(), cmdStr, rest);
910 break;
911 }
912 case CMD_SKIPTO: {
913 leavePara();
914 QString rest = getRestOfLine();
915 if (s_quoting) {
916 appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
917 appendAtom(Atom(Atom::CodeQuoteArgument, rest));
918 }
919 m_quoter.quoteTo(location(), cmdStr, rest);
920 break;
921 }
922 case CMD_SKIPUNTIL: {
923 leavePara();
924 QString rest = getRestOfLine();
925 if (s_quoting) {
926 appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
927 appendAtom(Atom(Atom::CodeQuoteArgument, rest));
928 }
929 m_quoter.quoteUntil(location(), cmdStr, rest);
930 break;
931 }
932 case CMD_SPAN:
933 p1 = ATOM_FORMATTING_SPAN + getArgument(ArgumentParsingOptions::Verbatim);
934 startFormat(p1, cmd);
935 break;
936 case CMD_SNIPPET: {
937 leavePara();
938 QString snippet = getArgument();
939 QString identifier = getRestOfLine();
940 if (s_quoting) {
941 appendAtom(Atom(Atom::SnippetCommand, cmdStr));
942 appendAtom(Atom(Atom::SnippetLocation, snippet));
943 appendAtom(Atom(Atom::SnippetIdentifier, identifier));
944 }
945 marker = CodeMarker::markerForFileName(snippet);
946 quoteFromFile(snippet);
947 appendToCode(m_quoter.quoteSnippet(location(), identifier), marker->atomType());
948 break;
949 }
950 case CMD_SUB:
951 startFormat(ATOM_FORMATTING_SUBSCRIPT, cmd);
952 break;
953 case CMD_SUP:
954 startFormat(ATOM_FORMATTING_SUPERSCRIPT, cmd);
955 break;
956 case CMD_TABLE:
957 leaveValueList();
958 p1 = getOptionalArgument();
959 p2 = getOptionalArgument();
960 if (openCommand(cmd)) {
961 leavePara();
962 appendAtom(Atom(Atom::TableLeft, p1, p2));
963 m_inTableHeader = false;
964 m_inTableRow = false;
965 m_inTableItem = false;
966 }
967 break;
969 p1 = "1";
970 if (isLeftBraceAhead())
971 p1 = getArgument();
972 p1 += QLatin1Char(',');
973 p1 += QString::number((int)getSectioningUnit());
974 appendAtom(Atom(Atom::TableOfContents, p1));
975 break;
976 case CMD_TARGET:
977 if (m_openedCommands.top() == CMD_TABLE && !m_inTableItem) {
978 location().warning("Found a \\target command outside table item in a table.\n"
979 "Move the \\target inside the \\li to resolve this warning.");
980 }
981 insertTarget(getRestOfLine());
982 break;
983 case CMD_TM:
984 // Ignore command while parsing \section<N> argument
985 if (m_paragraphState != InSingleLineParagraph)
986 startFormat(ATOM_FORMATTING_TRADEMARK, cmd);
987 break;
988 case CMD_TT:
989 startFormat(ATOM_FORMATTING_TELETYPE, cmd);
990 break;
991 case CMD_UICONTROL:
992 startFormat(ATOM_FORMATTING_UICONTROL, cmd);
993 break;
994 case CMD_UNDERLINE:
995 startFormat(ATOM_FORMATTING_UNDERLINE, cmd);
996 break;
997 case CMD_UNICODE: {
998 enterPara();
999 p1 = getArgument();
1000 bool ok;
1001 uint unicodeChar = p1.toUInt(&ok, 0);
1002 if (!ok || (unicodeChar == 0x0000) || (unicodeChar > 0xFFFE))
1003 location().warning(
1004 QStringLiteral("Invalid Unicode character '%1' specified with '%2'")
1005 .arg(p1, cmdName(CMD_UNICODE)));
1006 else
1007 appendAtom(Atom(Atom::String, QChar(unicodeChar)));
1008 break;
1009 }
1010 case CMD_VALUE:
1011 leaveValue();
1012 if (m_openedLists.top().style() == OpenedList::Value) {
1013 QString p2;
1014 p1 = getArgument();
1015 if (p1.startsWith(QLatin1String("[since "))
1016 && p1.endsWith(QLatin1String("]"))) {
1017 p2 = p1.mid(7, p1.size() - 8);
1018 p1 = getArgument();
1019 }
1020 if (!m_private->m_enumItemList.contains(p1))
1021 m_private->m_enumItemList.append(p1);
1022
1023 m_openedLists.top().next();
1024 appendAtom(Atom(Atom::ListTagLeft, ATOM_LIST_VALUE));
1025 appendAtom(Atom(Atom::String, p1));
1026 appendAtom(Atom(Atom::ListTagRight, ATOM_LIST_VALUE));
1027 if (!p2.isEmpty()) {
1028 appendAtom(Atom(Atom::SinceTagLeft, ATOM_LIST_VALUE));
1029 appendAtom(Atom(Atom::String, p2));
1030 appendAtom(Atom(Atom::SinceTagRight, ATOM_LIST_VALUE));
1031 }
1032 appendAtom(Atom(Atom::ListItemLeft, ATOM_LIST_VALUE));
1033
1034 skipSpacesOrOneEndl();
1035 if (isBlankLine())
1036 appendAtom(Atom(Atom::Nop));
1037 } else {
1038 // ### unknown problems
1039 }
1040 break;
1041 case CMD_WARNING:
1042 leavePara();
1043 enterPara(Atom::WarningLeft, Atom::WarningRight);
1044 break;
1045 case CMD_OVERLOAD:
1046 leavePara();
1047 m_private->m_metacommandsUsed.insert(cmdStr);
1048 p1.clear();
1049 if (!isBlankLine())
1050 p1 = getRestOfLine();
1051 if (!p1.isEmpty()) {
1052 appendAtom(Atom(Atom::ParaLeft));
1053 appendAtom(Atom(Atom::String, "This function overloads "));
1054 appendAtom(Atom(Atom::AutoLink, p1));
1055 appendAtom(Atom(Atom::String, "."));
1056 appendAtom(Atom(Atom::ParaRight));
1057 } else {
1058 appendAtom(Atom(Atom::ParaLeft));
1059 appendAtom(Atom(Atom::String, "This is an overloaded function."));
1060 appendAtom(Atom(Atom::ParaRight));
1061 p1 = getMetaCommandArgument(cmdStr);
1062 }
1063 m_private->m_metaCommandMap[cmdStr].append(ArgPair(p1, QString()));
1064 break;
1065 case NOT_A_CMD:
1066 if (metaCommandSet.contains(cmdStr)) {
1067 QString arg;
1068 QString bracketedArg;
1069 m_private->m_metacommandsUsed.insert(cmdStr);
1070 if (isLeftBracketAhead())
1071 bracketedArg = getBracketedArgument();
1072 // Force a linebreak after \obsolete or \deprecated
1073 // to treat potential arguments as a new text paragraph.
1074 if (m_position < m_inputLength
1075 && (cmdStr == QLatin1String("obsolete")
1076 || cmdStr == QLatin1String("deprecated")))
1077 m_input[m_position] = '\n';
1078 else
1079 arg = getMetaCommandArgument(cmdStr);
1080 m_private->m_metaCommandMap[cmdStr].append(ArgPair(arg, bracketedArg));
1081 if (possibleTopics.contains(cmdStr)) {
1082 if (!cmdStr.endsWith(QLatin1String("propertygroup")))
1083 m_private->m_topics.append(Topic(cmdStr, arg));
1084 }
1085 } else if (s_utilities.macroHash.contains(cmdStr)) {
1086 const Macro &macro = s_utilities.macroHash.value(cmdStr);
1087 QStringList macroArgs;
1088 int numPendingFi = 0;
1089 int numFormatDefs = 0;
1090 for (auto it = macro.m_otherDefs.constBegin();
1091 it != macro.m_otherDefs.constEnd(); ++it) {
1092 if (it.key() != "match") {
1093 if (numFormatDefs == 0)
1094 macroArgs = getMacroArguments(cmdStr, macro);
1095 appendAtom(Atom(Atom::FormatIf, it.key()));
1096 expandMacro(*it, macroArgs);
1097 ++numFormatDefs;
1098 if (it == macro.m_otherDefs.constEnd()) {
1099 appendAtom(Atom(Atom::FormatEndif));
1100 } else {
1101 appendAtom(Atom(Atom::FormatElse));
1102 ++numPendingFi;
1103 }
1104 }
1105 }
1106 while (numPendingFi-- > 0)
1107 appendAtom(Atom(Atom::FormatEndif));
1108
1109 if (!macro.m_defaultDef.isEmpty()) {
1110 if (numFormatDefs > 0) {
1111 macro.m_defaultDefLocation.warning(
1112 QStringLiteral("Macro cannot have both "
1113 "format-specific and qdoc-"
1114 "syntax definitions"));
1115 } else {
1116 QString expanded = expandMacroToString(cmdStr, macro);
1117 m_input.replace(m_backslashPosition,
1118 m_endPosition - m_backslashPosition, expanded);
1119 m_inputLength = m_input.size();
1120 m_position = m_backslashPosition;
1121 }
1122 }
1123 } else if (isAutoLinkString(cmdStr)) {
1124 appendWord(cmdStr);
1125 } else {
1126 if (!cmdStr.endsWith("propertygroup")) {
1127 // The QML property group commands are no longer required
1128 // for grouping QML properties. They are allowed but ignored.
1129 location().warning(QStringLiteral("Unknown command '\\%1'").arg(cmdStr),
1130 detailsUnknownCommand(metaCommandSet, cmdStr));
1131 }
1132 enterPara();
1133 appendAtom(Atom(Atom::UnknownCommand, cmdStr));
1134 }
1135 }
1136 } // case '\\' (qdoc markup command)
1137 break;
1138 }
1139 case '-': { // Catch en-dash (--) and em-dash (---) markup here.
1140 enterPara();
1141 qsizetype dashCount = 1;
1142 ++m_position;
1143
1144 // Figure out how many hyphens in a row.
1145 while ((m_position < m_inputLength) && (m_input.at(m_position) == '-')) {
1146 ++dashCount;
1147 ++m_position;
1148 }
1149
1150 if (dashCount == 3) {
1151 // 3 hyphens, append an em-dash character.
1152 const QChar emDash(8212);
1153 appendChar(emDash);
1154 } else if (dashCount == 2) {
1155 // 2 hyphens; append an en-dash character.
1156 const QChar enDash(8211);
1157 appendChar(enDash);
1158 } else {
1159 // dashCount is either one or more than three. Append a hyphen
1160 // the appropriate number of times. This ensures '----' doesn't
1161 // end up as an em-dash followed by a hyphen in the output.
1162 for (qsizetype i = 0; i < dashCount; ++i)
1163 appendChar('-');
1164 }
1165 break;
1166 }
1167 case '{':
1168 enterPara();
1169 appendChar('{');
1170 ++m_braceDepth;
1171 ++m_position;
1172 break;
1173 case '}': {
1174 --m_braceDepth;
1175 ++m_position;
1176
1177 auto format = m_pendingFormats.find(m_braceDepth);
1178 if (format == m_pendingFormats.end()) {
1179 enterPara();
1180 appendChar('}');
1181 } else {
1182 const auto &last{m_private->m_text.lastAtom()->string()};
1183 appendAtom(Atom(Atom::FormattingRight, *format));
1184 if (*format == ATOM_FORMATTING_INDEX) {
1185 if (m_indexStartedParagraph)
1186 skipAllSpaces();
1187 } else if (*format == ATOM_FORMATTING_LINK) {
1188 // hack for C++ to support links like
1189 // \l{QString::}{count()}
1190 if (currentLinkAtom && currentLinkAtom->string().endsWith("::")) {
1191 QString suffix =
1192 Text::subText(currentLinkAtom, m_private->m_text.lastAtom())
1193 .toString();
1194 currentLinkAtom->concatenateString(suffix);
1195 }
1196 currentLinkAtom = nullptr;
1197 } else if (*format == ATOM_FORMATTING_TRADEMARK) {
1198 m_private->m_text.lastAtom()->append(last);
1199 }
1200 m_pendingFormats.erase(format);
1201 }
1202 break;
1203 }
1204 // Do not parse content after '//!' comments
1205 case '/': {
1206 if (m_position + 2 < m_inputLength)
1207 if (m_input.at(m_position + 1) == '/')
1208 if (m_input.at(m_position + 2) == '!') {
1209 m_position += 2;
1210 getRestOfLine();
1211 if (m_input.at(m_position - 1) == '\n')
1212 --m_position;
1213 break;
1214 }
1215 Q_FALLTHROUGH(); // fall through
1216 }
1217 default: {
1218 bool newWord;
1219 switch (m_private->m_text.lastAtom()->type()) {
1220 case Atom::ParaLeft:
1221 newWord = true;
1222 break;
1223 default:
1224 newWord = false;
1225 }
1226
1227 if (m_paragraphState == OutsideParagraph) {
1228 if (ch.isSpace()) {
1229 ++m_position;
1230 newWord = false;
1231 } else {
1232 enterPara();
1233 newWord = true;
1234 }
1235 } else {
1236 if (ch.isSpace()) {
1237 ++m_position;
1238 if ((ch == '\n')
1239 && (m_paragraphState == InSingleLineParagraph || isBlankLine())) {
1240 leavePara();
1241 newWord = false;
1242 } else {
1243 appendChar(' ');
1244 newWord = true;
1245 }
1246 } else {
1247 newWord = true;
1248 }
1249 }
1250
1251 if (newWord) {
1252 qsizetype startPos = m_position;
1253 // No auto-linking inside links
1254 bool autolink = (!m_pendingFormats.isEmpty() &&
1255 m_pendingFormats.last() == ATOM_FORMATTING_LINK) ?
1256 false : isAutoLinkString(m_input, m_position);
1257 if (m_position == startPos) {
1258 if (!ch.isSpace()) {
1259 appendChar(ch);
1260 ++m_position;
1261 }
1262 } else {
1263 QString word = m_input.mid(startPos, m_position - startPos);
1264 if (autolink) {
1265 if (s_ignoreWords.contains(word) || word.startsWith(QString("__")))
1266 appendWord(word);
1267 else
1268 appendAtom(Atom(Atom::AutoLink, word));
1269 } else {
1270 appendWord(word);
1271 }
1272 }
1273 }
1274 } // default:
1275 } // switch (ch.unicode())
1276 }
1277 leaveValueList();
1278
1279 // for compatibility
1280 if (m_openedCommands.top() == CMD_LEGALESE) {
1281 appendAtom(Atom(Atom::LegaleseRight));
1282 m_openedCommands.pop();
1283 }
1284
1285 if (m_openedCommands.top() != CMD_OMIT) {
1286 location().warning(
1287 QStringLiteral("Missing '\\%1'").arg(endCmdName(m_openedCommands.top())));
1288 } else if (preprocessorSkipping.size() > 0) {
1289 location().warning(QStringLiteral("Missing '\\%1'").arg(cmdName(CMD_ENDIF)));
1290 }
1291
1292 if (m_currentSection > Doc::NoSection) {
1293 appendAtom(Atom(Atom::SectionRight, QString::number(m_currentSection)));
1294 m_currentSection = Doc::NoSection;
1295 }
1296
1298}
1299
1300/*!
1301 Returns the current location.
1302 */
1303Location &DocParser::location()
1304{
1305 while (!m_openedInputs.isEmpty() && m_openedInputs.top() <= m_position) {
1306 m_cachedLocation.pop();
1307 m_cachedPosition = m_openedInputs.pop();
1308 }
1309 while (m_cachedPosition < m_position)
1310 m_cachedLocation.advance(m_input.at(m_cachedPosition++));
1311 return m_cachedLocation;
1312}
1313
1314QString DocParser::detailsUnknownCommand(const QSet<QString> &metaCommandSet, const QString &str)
1315{
1316 QSet<QString> commandSet = metaCommandSet;
1317 int i = 0;
1318 while (cmds[i].name != nullptr) {
1319 commandSet.insert(cmds[i].name);
1320 ++i;
1321 }
1322
1323 QString best = nearestName(str, commandSet);
1324 if (best.isEmpty())
1325 return QString();
1326 return QStringLiteral("Maybe you meant '\\%1'?").arg(best);
1327}
1328
1329/*!
1330 \internal
1331
1332 Issues a warning about an empty or duplicate definition of either a
1333 \\target or \\keyword command (determined by \a cmdString) at
1334 \a location. \a duplicateDefinition is the target being processed;
1335 the already registered definition is \a previousDefinition.
1336 */
1337static void warnAboutEmptyOrPreexistingTarget(const Location &location, const QString &duplicateDefinition,
1338 const QString &cmdString, const QString &previousDefinition)
1339{
1340 if (duplicateDefinition.isEmpty()) {
1341 location.warning("Expected an argument for \\%1"_L1.arg(cmdString));
1342 } else {
1343 location.warning(
1344 "Duplicate %3 name '%1'. The previous occurrence is here: %2"_L1
1345 .arg(duplicateDefinition, previousDefinition, cmdString));
1346 }
1347}
1348
1349/*!
1350 \internal
1351
1352 \brief Registers \a target as a linkable entity.
1353
1354 The main purpose of this method is to register a target as defined
1355 along with its location, so that becomes a valid link target from other
1356 parts of the documentation.
1357
1358 If the \a target name is already registered, a warning is issued,
1359 as multiple definitions are problematic.
1360
1361 \sa insertKeyword, target-command
1362 */
1363void DocParser::insertTarget(const QString &target)
1364{
1365 if (target.isEmpty() || m_targetMap.contains(target))
1366 return warnAboutEmptyOrPreexistingTarget(location(), target,
1367 s_utilities.cmdHash.key(CMD_TARGET), m_targetMap[target].toString());
1368
1369 m_targetMap.insert(target, location());
1370 m_private->constructExtra();
1371
1372 appendAtom(Atom(Atom::Target, target));
1373 m_private->extra->m_targets.append(m_private->m_text.lastAtom());
1374}
1375
1376/*!
1377 \internal
1378
1379 \brief Registers \a keyword as a linkable entity.
1380
1381 The main purpose of this method is to register a keyword as defined
1382 along with its location, so that becomes a valid link target from other
1383 parts of the documentation.
1384
1385 If the \a keyword name is already registered, a warning is issued,
1386 as multiple definitions are problematic.
1387
1388 \sa insertTarget, keyword-command
1389 */
1390void DocParser::insertKeyword(const QString &keyword)
1391{
1392 if (keyword.isEmpty() || m_targetMap.contains(keyword))
1393 return warnAboutEmptyOrPreexistingTarget(location(), keyword,
1394 s_utilities.cmdHash.key(CMD_KEYWORD), m_targetMap[keyword].toString());
1395
1396 m_targetMap.insert(keyword, location());
1397 m_private->constructExtra();
1398
1399 appendAtom(Atom(Atom::Keyword, keyword));
1400 m_private->extra->m_keywords.append(m_private->m_text.lastAtom());
1401}
1402
1403void DocParser::include(const QString &fileName, const QString &identifier, const QStringList &parameters)
1404{
1405 if (location().depth() > 16)
1406 location().fatal(QStringLiteral("Too many nested '\\%1's").arg(cmdName(CMD_INCLUDE)));
1407 QString filePath = Config::instance().getIncludeFilePath(fileName);
1408 if (filePath.isEmpty()) {
1409 location().warning(QStringLiteral("Cannot find qdoc include file '%1'").arg(fileName));
1410 } else {
1411 QFile inFile(filePath);
1412 if (!inFile.open(QFile::ReadOnly)) {
1413 location().warning(
1414 QStringLiteral("Cannot open qdoc include file '%1'").arg(filePath));
1415 } else {
1416 location().push(fileName);
1417 QTextStream inStream(&inFile);
1418 QString includedContent = inStream.readAll();
1419 inFile.close();
1420
1421 if (identifier.isEmpty()) {
1422 expandArgumentsInString(includedContent, parameters);
1423 m_input.insert(m_position, includedContent);
1424 m_inputLength = m_input.size();
1425 m_openedInputs.push(m_position + includedContent.size());
1426 } else {
1427 QStringList lineBuffer = includedContent.split(QLatin1Char('\n'));
1428 qsizetype bufLen{lineBuffer.size()};
1429 qsizetype i;
1430 QStringView trimmedLine;
1431 for (i = 0; i < bufLen; ++i) {
1432 trimmedLine = QStringView{lineBuffer[i]}.trimmed();
1433 if (trimmedLine.startsWith(QLatin1String("//!")) &&
1434 trimmedLine.contains(identifier))
1435 break;
1436 }
1437 if (i < bufLen - 1) {
1438 ++i;
1439 } else {
1440 location().warning(
1441 QStringLiteral("Cannot find '%1' in '%2'").arg(identifier, filePath));
1442 return;
1443 }
1444 QString result;
1445 do {
1446 trimmedLine = QStringView{lineBuffer[i]}.trimmed();
1447 if (trimmedLine.startsWith(QLatin1String("//!")) &&
1448 trimmedLine.contains(identifier))
1449 break;
1450 else
1451 result += lineBuffer[i] + QLatin1Char('\n');
1452 ++i;
1453 } while (i < bufLen);
1454
1455 expandArgumentsInString(result, parameters);
1456 if (result.isEmpty()) {
1457 location().warning(QStringLiteral("Empty qdoc snippet '%1' in '%2'")
1458 .arg(identifier, filePath));
1459 } else {
1460 m_input.insert(m_position, result);
1461 m_inputLength = m_input.size();
1462 m_openedInputs.push(m_position + result.size());
1463 }
1464 }
1465 }
1466 }
1467}
1468
1469void DocParser::startFormat(const QString &format, int cmd)
1470{
1471 enterPara();
1472
1473 for (const auto &item : std::as_const(m_pendingFormats)) {
1474 if (item == format) {
1475 location().warning(QStringLiteral("Cannot nest '\\%1' commands").arg(cmdName(cmd)));
1476 return;
1477 }
1478 }
1479
1480 appendAtom(Atom(Atom::FormattingLeft, format));
1481
1482 if (isLeftBraceAhead()) {
1483 skipSpacesOrOneEndl();
1484 m_pendingFormats.insert(m_braceDepth, format);
1485 ++m_braceDepth;
1486 ++m_position;
1487 } else {
1488 const auto &arg{getArgument()};
1489 appendAtom(Atom(Atom::String, arg));
1490 appendAtom(Atom(Atom::FormattingRight, format));
1491 if (format == ATOM_FORMATTING_INDEX && m_indexStartedParagraph) {
1492 skipAllSpaces();
1493 m_indexStartedParagraph = false;
1494 } else if (format == ATOM_FORMATTING_TRADEMARK) {
1495 m_private->m_text.lastAtom()->append(arg);
1496 }
1497 }
1498}
1499
1500bool DocParser::openCommand(int cmd)
1501{
1502 int outer = m_openedCommands.top();
1503 bool ok = true;
1504
1505 if (cmd == CMD_COMPARESWITH && m_openedCommands.contains(cmd)) {
1506 location().warning(u"Cannot nest '\\%1' commands"_s.arg(cmdName(cmd)));
1507 return false;
1508 } else if (cmd != CMD_LINK) {
1509 if (outer == CMD_LIST) {
1510 ok = (cmd == CMD_FOOTNOTE || cmd == CMD_LIST);
1511 } else if (outer == CMD_SIDEBAR) {
1512 ok = (cmd == CMD_LIST || cmd == CMD_QUOTATION || cmd == CMD_SIDEBAR);
1513 } else if (outer == CMD_QUOTATION) {
1514 ok = (cmd == CMD_LIST);
1515 } else if (outer == CMD_TABLE) {
1516 ok = (cmd == CMD_LIST || cmd == CMD_FOOTNOTE || cmd == CMD_QUOTATION);
1517 } else if (outer == CMD_FOOTNOTE || outer == CMD_LINK) {
1518 ok = false;
1519 }
1520 }
1521
1522 if (ok) {
1523 m_openedCommands.push(cmd);
1524 } else {
1525 location().warning(
1526 QStringLiteral("Can't use '\\%1' in '\\%2'").arg(cmdName(cmd), cmdName(outer)));
1527 }
1528 return ok;
1529}
1530
1531/*!
1532 Returns \c true if \a word qualifies for auto-linking.
1533
1534 A word qualifies for auto-linking if either:
1535
1536 \list
1537 \li It is composed of only upper and lowercase characters
1538 \li AND It contains at least one uppercase character that is not
1539 the first character of word
1540 \li AND it contains at least two lowercase characters
1541 \endlist
1542
1543 Or
1544
1545 \list
1546 \li It is composed only of uppercase characters, lowercase
1547 characters, characters in [_@] and the \c {"::"} sequence.
1548 \li It contains at least one uppercase character that is not
1549 the first character of word or it contains at least one
1550 lowercase character
1551 \li AND it contains at least one character in [_@] or it
1552 contains at least one \c {"::"} sequence.
1553 \endlist
1554
1555 Inserting or suffixing, but not prefixing, any sequence in [0-9]+
1556 in a word that qualifies for auto-linking by the above rules
1557 preserves the auto-linkability of the word.
1558
1559 Suffixing the sequence \c {"()"} to a word that qualifies for
1560 auto-linking by the above rules preserves the auto-linkability of
1561 a word.
1562
1563 FInally, a word qualifies for auto-linking if:
1564
1565 \list
1566 \li It is composed of only uppercase characters, lowercase
1567 characters and the sequence \c {"()"}
1568 \li AND it contains one lowercase character and a sequence of zero, one
1569 or two upper or lowercase characters
1570 \li AND it contains exactly one sequence \c {"()"}
1571 \li AND it contains one sequence \c {"()"} as the last two
1572 characters of word
1573 \endlist
1574
1575 For example, \c {"fOo"}, \c {"FooBar"} and \c {"foobaR"} qualify
1576 for auto-linking by the first rule.
1577
1578 \c {"QT_DEBUG"}, \c {"::Qt"} and \c {"std::move"} qualifies for
1579 auto-linking by the second rule.
1580
1581 \c {"SIMDVector256"} qualifies by suffixing \c {"SIMDVector"},
1582 which qualifies by the first rule, with the sequence \c {"256"}
1583
1584 \c {"FooBar::Bar()"} qualifies by suffixing \c {"FooBar::Bar"},
1585 which qualifies by the first and second rule, with the sequence \c
1586 {"()"}.
1587
1588 \c {"Foo()"} and \c {"a()"} qualifies by the last rule.
1589
1590 Instead, \c {"Q"}, \c {"flower"}, \c {"_"} and \c {"()"} do not
1591 qualify for auto-linking.
1592
1593 The rules are intended as a heuristic to catch common cases in the
1594 Qt documentation where a word might represent an important
1595 documented element such as a class or a method that could be
1596 linked to while at the same time avoiding catching common words
1597 such as \c {"A"} or \c {"Nonetheless"}.
1598
1599 The heuristic assumes that Qt's codebase respects a style where
1600 camelCasing is the standard for most of the elements, a function
1601 call is identified by the use of parenthesis and certain elements,
1602 such as macros, might be fully uppercase.
1603
1604 Furthemore, it assumes that the Qt codebase is written in a
1605 language that has an identifier grammar similar to the one for
1606 C++.
1607*/
1608inline bool DocParser::isAutoLinkString(const QString &word)
1609{
1610 qsizetype start = 0;
1611 return isAutoLinkString(word, start) && (start == word.size());
1612}
1613
1614/*!
1615 Returns \c true if a prefix of a substring of \a word qualifies
1616 for auto-linking.
1617
1618 Respects the same parsing rules as the unary overload.
1619
1620 \a curPos defines the offset, from the first character of \ word,
1621 at which the parsed substring starts.
1622
1623 When the call completes, \a curPos represents the offset, from the
1624 first character of word, that is the successor of the offset of
1625 the last parsed character.
1626
1627 If the return value of the call is \c true, it is guaranteed that
1628 the prefix of the substring of \word that contains the characters
1629 from the initial value of \a curPos and up to but not including \a
1630 curPos qualifies for auto-linking.
1631
1632 If \a curPos is initially zero, the considered substring is the
1633 entirety of \a word.
1634*/
1635bool DocParser::isAutoLinkString(const QString &word, qsizetype &curPos)
1636{
1637 qsizetype len = word.size();
1638 qsizetype startPos = curPos;
1639 int numUppercase = 0;
1640 int numLowercase = 0;
1641 int numStrangeSymbols = 0;
1642
1643 while (curPos < len) {
1644 unsigned char latin1Ch = word.at(curPos).toLatin1();
1645 if (islower(latin1Ch)) {
1646 ++numLowercase;
1647 ++curPos;
1648 } else if (isupper(latin1Ch)) {
1649 if (curPos > startPos)
1650 ++numUppercase;
1651 ++curPos;
1652 } else if (isdigit(latin1Ch)) {
1653 if (curPos > startPos)
1654 ++curPos;
1655 else
1656 break;
1657 } else if (latin1Ch == '_' || latin1Ch == '@') {
1658 ++numStrangeSymbols;
1659 ++curPos;
1660 } else if ((latin1Ch == ':') && (curPos < len - 1)
1661 && (word.at(curPos + 1) == QLatin1Char(':'))) {
1662 ++numStrangeSymbols;
1663 curPos += 2;
1664 } else if (latin1Ch == '(') {
1665 if ((curPos < len - 1) && (word.at(curPos + 1) == QLatin1Char(')'))) {
1666 ++numStrangeSymbols;
1667 m_position += 2;
1668 }
1669
1670 break;
1671 } else {
1672 break;
1673 }
1674 }
1675
1676 return ((numUppercase >= 1 && numLowercase >= 2) || (numStrangeSymbols > 0 && (numUppercase + numLowercase >= 1)));
1677}
1678
1679bool DocParser::closeCommand(int endCmd)
1680{
1681 if (endCmdFor(m_openedCommands.top()) == endCmd && m_openedCommands.size() > 1) {
1682 m_openedCommands.pop();
1683 return true;
1684 } else {
1685 bool contains = false;
1686 QStack<int> opened2 = m_openedCommands;
1687 while (opened2.size() > 1) {
1688 if (endCmdFor(opened2.top()) == endCmd) {
1689 contains = true;
1690 break;
1691 }
1692 opened2.pop();
1693 }
1694
1695 if (contains) {
1696 while (endCmdFor(m_openedCommands.top()) != endCmd && m_openedCommands.size() > 1) {
1697 location().warning(
1698 QStringLiteral("Missing '\\%1' before '\\%2'")
1699 .arg(endCmdName(m_openedCommands.top()), cmdName(endCmd)));
1700 m_openedCommands.pop();
1701 }
1702 } else {
1703 location().warning(QStringLiteral("Unexpected '\\%1'").arg(cmdName(endCmd)));
1704 }
1705 return false;
1706 }
1707}
1708
1709void DocParser::startSection(Doc::Sections unit, int cmd)
1710{
1711 leaveValueList();
1712
1713 if (m_currentSection == Doc::NoSection) {
1714 m_currentSection = static_cast<Doc::Sections>(unit);
1715 m_private->constructExtra();
1716 } else {
1717 endSection(unit, cmd);
1718 }
1719
1720 appendAtom(Atom(Atom::SectionLeft, QString::number(unit)));
1721 m_private->constructExtra();
1722 m_private->extra->m_tableOfContents.append(m_private->m_text.lastAtom());
1723 m_private->extra->m_tableOfContentsLevels.append(unit);
1724 enterPara(Atom::SectionHeadingLeft, Atom::SectionHeadingRight, QString::number(unit));
1725 m_currentSection = unit;
1726}
1727
1728void DocParser::endSection(int, int) // (int unit, int endCmd)
1729{
1730 leavePara();
1731 appendAtom(Atom(Atom::SectionRight, QString::number(m_currentSection)));
1732 m_currentSection = (Doc::NoSection);
1733}
1734
1735/*!
1736 \internal
1737 \brief Parses arguments to QDoc's see also command.
1738
1739 Parses space or comma separated arguments passed to the \\sa command.
1740 Multi-line input requires that the arguments are comma separated. Wrap
1741 arguments in curly braces for multi-word targets, and for scope resolution
1742 (for example, {QString::}{count()}).
1743
1744 This method updates the list of links for the See also section.
1745
1746 \sa {DocPrivate::}{addAlso()}, getArgument()
1747 */
1748void DocParser::parseAlso()
1749{
1750 auto line_comment = [this]() -> bool {
1751 skipSpacesOnLine();
1752 if (m_position + 2 > m_inputLength)
1753 return false;
1754 if (m_input[m_position].unicode() == '/') {
1755 if (m_input[m_position + 1].unicode() == '/') {
1756 if (m_input[m_position + 2].unicode() == '!') {
1757 return true;
1758 }
1759 }
1760 }
1761 return false;
1762 };
1763
1764 auto skip_everything_until_newline = [this]() -> void {
1765 while (m_position < m_inputLength && m_input[m_position] != '\n')
1766 ++m_position;
1767 };
1768
1769 leavePara();
1770 skipSpacesOnLine();
1771 while (m_position < m_inputLength && m_input[m_position] != '\n') {
1772 QString target;
1773 QString str;
1774 bool skipMe = false;
1775
1776 if (m_input[m_position] == '{') {
1777 target = getArgument();
1778 skipSpacesOnLine();
1779 if (m_position < m_inputLength && m_input[m_position] == '{') {
1780 str = getArgument();
1781
1782 // hack for C++ to support links like \l{QString::}{count()}
1783 if (target.endsWith("::"))
1784 target += str;
1785 } else {
1786 str = target;
1787 }
1788 } else {
1789 target = getArgument();
1790 str = cleanLink(target);
1791 if (target == QLatin1String("and") || target == QLatin1String("."))
1792 skipMe = true;
1793 }
1794
1795 if (!skipMe) {
1796 Text also;
1797 also << Atom(Atom::Link, target) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1799 m_private->addAlso(also);
1800 }
1801
1802 skipSpacesOnLine();
1803
1804 if (line_comment())
1805 skip_everything_until_newline();
1806
1807 if (m_position < m_inputLength && m_input[m_position] == ',') {
1808 m_position++;
1809 if (line_comment())
1810 skip_everything_until_newline();
1811 skipSpacesOrOneEndl();
1812 } else if (m_position >= m_inputLength || m_input[m_position] != '\n') {
1813 location().warning(QStringLiteral("Missing comma in '\\%1'").arg(cmdName(CMD_SA)));
1814 }
1815 }
1816}
1817
1818void DocParser::appendAtom(const Atom& atom) {
1819 m_private->m_text << atom;
1820}
1821
1822void DocParser::appendAtom(const LinkAtom& atom) {
1823 m_private->m_text << atom;
1824}
1825
1826void DocParser::appendChar(QChar ch)
1827{
1829 appendAtom(Atom(Atom::String));
1830 Atom *atom = m_private->m_text.lastAtom();
1831 if (ch == QLatin1Char(' ')) {
1832 if (!atom->string().endsWith(QLatin1Char(' ')))
1833 atom->appendChar(QLatin1Char(' '));
1834 } else
1835 atom->appendChar(ch);
1836}
1837
1838void DocParser::appendWord(const QString &word)
1839{
1840 if (m_private->m_text.lastAtom()->type() != Atom::String) {
1841 appendAtom(Atom(Atom::String, word));
1842 } else
1843 m_private->m_text.lastAtom()->concatenateString(word);
1844}
1845
1846void DocParser::appendToCode(const QString &markedCode)
1847{
1848 if (!isCode(m_lastAtom)) {
1849 appendAtom(Atom(Atom::Code));
1850 m_lastAtom = m_private->m_text.lastAtom();
1851 }
1852 m_lastAtom->concatenateString(markedCode);
1853}
1854
1855void DocParser::appendToCode(const QString &markedCode, Atom::AtomType defaultType)
1856{
1857 if (!isCode(m_lastAtom)) {
1858 appendAtom(Atom(defaultType, markedCode));
1859 m_lastAtom = m_private->m_text.lastAtom();
1860 } else {
1861 m_lastAtom->concatenateString(markedCode);
1862 }
1863}
1864
1865void DocParser::enterPara(Atom::AtomType leftType, Atom::AtomType rightType, const QString &string)
1866{
1867 if (m_paragraphState != OutsideParagraph)
1868 return;
1869
1873 leaveValueList();
1874 }
1875
1876 appendAtom(Atom(leftType, string));
1877 m_indexStartedParagraph = false;
1878 m_pendingParagraphLeftType = leftType;
1879 m_pendingParagraphRightType = rightType;
1880 m_pendingParagraphString = string;
1881 if (leftType == Atom::SectionHeadingLeft) {
1882 m_paragraphState = InSingleLineParagraph;
1883 } else {
1884 m_paragraphState = InMultiLineParagraph;
1885 }
1886 skipSpacesOrOneEndl();
1887}
1888
1889void DocParser::leavePara()
1890{
1891 if (m_paragraphState == OutsideParagraph)
1892 return;
1893
1894 if (!m_pendingFormats.isEmpty()) {
1895 location().warning(QStringLiteral("Missing '}'"));
1896 m_pendingFormats.clear();
1897 }
1898
1899 if (m_private->m_text.lastAtom()->type() == m_pendingParagraphLeftType) {
1900 m_private->m_text.stripLastAtom();
1901 } else {
1903 && m_private->m_text.lastAtom()->string().endsWith(QLatin1Char(' '))) {
1905 }
1906 appendAtom(Atom(m_pendingParagraphRightType, m_pendingParagraphString));
1907 }
1908 m_paragraphState = OutsideParagraph;
1909 m_indexStartedParagraph = false;
1910 m_pendingParagraphRightType = Atom::Nop;
1911 m_pendingParagraphString.clear();
1912}
1913
1914void DocParser::leaveValue()
1915{
1916 leavePara();
1917 if (m_openedLists.isEmpty()) {
1918 m_openedLists.push(OpenedList(OpenedList::Value));
1919 appendAtom(Atom(Atom::ListLeft, ATOM_LIST_VALUE));
1920 } else {
1921 if (m_private->m_text.lastAtom()->type() == Atom::Nop)
1922 m_private->m_text.stripLastAtom();
1923 appendAtom(Atom(Atom::ListItemRight, ATOM_LIST_VALUE));
1924 }
1925}
1926
1927void DocParser::leaveValueList()
1928{
1929 leavePara();
1930 if (!m_openedLists.isEmpty() && (m_openedLists.top().style() == OpenedList::Value)) {
1931 if (m_private->m_text.lastAtom()->type() == Atom::Nop)
1932 m_private->m_text.stripLastAtom();
1933 appendAtom(Atom(Atom::ListItemRight, ATOM_LIST_VALUE));
1934 appendAtom(Atom(Atom::ListRight, ATOM_LIST_VALUE));
1935 m_openedLists.pop();
1936 }
1937}
1938
1939void DocParser::leaveTableRow()
1940{
1941 if (m_inTableItem) {
1942 leavePara();
1943 appendAtom(Atom(Atom::TableItemRight));
1944 m_inTableItem = false;
1945 }
1946 if (m_inTableHeader) {
1947 appendAtom(Atom(Atom::TableHeaderRight));
1948 m_inTableHeader = false;
1949 }
1950 if (m_inTableRow) {
1951 appendAtom(Atom(Atom::TableRowRight));
1952 m_inTableRow = false;
1953 }
1954}
1955
1956void DocParser::quoteFromFile(const QString &filename)
1957{
1958 // KLUDGE: We dereference file_resolver as it is temporarily a pointer.
1959 // See the comment for file_resolver in the header files for more context.
1960 //
1961 // We spefically dereference it, instead of using the arrow
1962 // operator, to better represent that we do not consider this as
1963 // an actual pointer, as it should not be.
1964 //
1965 // Do note that we are considering it informally safe to
1966 // dereference the pointer, as we expect it to always hold a value
1967 // at this point, but actual enforcement of this appears nowhere
1968 // in the codebase.
1969 auto maybe_resolved_file{(*file_resolver).resolve(filename)};
1970 if (!maybe_resolved_file) {
1971 // TODO: [uncentralized-admonition][failed-resolve-file]
1972 // This warning is required in multiple places.
1973 // To ensure the consistency of the warning and avoid
1974 // duplicating code everywhere, provide a centralized effort
1975 // where the warning message can be generated (but not
1976 // issued).
1977 // The current format is based on what was used before, review
1978 // it when it is moved out.
1979 QString details = std::transform_reduce(
1980 (*file_resolver).get_search_directories().cbegin(),
1981 (*file_resolver).get_search_directories().cend(),
1982 u"Searched directories:"_s,
1983 std::plus(),
1984 [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
1985 );
1986
1987 location().warning(u"Cannot find file to quote from: %1"_s.arg(filename), details);
1988
1989 // REMARK: The following is duplicated from
1990 // Doc::quoteFromFile. If, for some reason (such as a file
1991 // that is inaccessible), the quoting fails but, previously,
1992 // the logic duplicated here was still run.
1993 // This is not true anymore as quoteFromFile does require a
1994 // resolved file to be run now.
1995 // It is not entirely clear if this is required for the
1996 // semantics of DocParser to be preserved, but for the sake of
1997 // avoiding premature breakages this was retained.
1998 // Do note that this should be considered temporary as the
1999 // quoter state, if any will be preserved, should not be
2000 // managed in such a spread and unlocal way.
2001 m_quoter.reset();
2002
2003 CodeMarker *marker = CodeMarker::markerForFileName(QString{});
2004 m_quoter.quoteFromFile(filename, QString{}, marker->markedUpCode(QString{}, nullptr, location()));
2005 } else Doc::quoteFromFile(location(), m_quoter, *maybe_resolved_file);
2006}
2007
2008/*!
2009 Expands a macro in-place in input.
2010
2011 Expects the current \e pos in the input to point to a backslash, and the macro to have a
2012 default definition. Format-specific macros are not expanded.
2013
2014 Behavior depends on \a options:
2015
2016 \value ArgumentParsingOptions::Default
2017 Default macro expansion; the string following the backslash
2018 must be a macro with a default definition.
2019 \value ArgumentParsingOptions::Verbatim
2020 The string following the backslash is rendered verbatim;
2021 No macro expansion is performed.
2022 \value ArgumentParsingOptions::MacroArguments
2023 Used for parsing argument(s) for a macro. Allows expanding
2024 macros, and also preserves a subset of commands (formatting
2025 commands) within the macro argument.
2026
2027 \note In addition to macros, a valid use for a backslash in an argument include
2028 escaping non-alnum characters, and splitting a single argument across multiple
2029 lines by escaping newlines. Escaping is also handled here.
2030
2031 Returns \c true on successful macro expansion.
2032 */
2033bool DocParser::expandMacro(ArgumentParsingOptions options)
2034{
2035 Q_ASSERT(m_input[m_position].unicode() == '\\');
2036
2037 if (options == ArgumentParsingOptions::Verbatim)
2038 return false;
2039
2040 QString cmdStr;
2041 qsizetype backslashPos = m_position++;
2042 while (m_position < m_input.size() && m_input[m_position].isLetterOrNumber())
2043 cmdStr += m_input[m_position++];
2044
2045 m_endPosition = m_position;
2046 if (!cmdStr.isEmpty()) {
2047 if (s_utilities.macroHash.contains(cmdStr)) {
2048 const Macro &macro = s_utilities.macroHash.value(cmdStr);
2049 if (!macro.m_defaultDef.isEmpty()) {
2050 QString expanded = expandMacroToString(cmdStr, macro);
2051 m_input.replace(backslashPos, m_position - backslashPos, expanded);
2052 m_inputLength = m_input.size();
2053 m_position = backslashPos;
2054 return true;
2055 } else {
2056 location().warning("Macro '%1' does not have a default definition"_L1.arg(cmdStr));
2057 }
2058 } else {
2059 int cmd = s_utilities.cmdHash.value(cmdStr, NOT_A_CMD);
2060 m_position = backslashPos;
2061 if (options != ArgumentParsingOptions::MacroArguments
2062 || cmd == NOT_A_CMD || !cmds[cmd].is_formatting_command) {
2063 location().warning("Unknown macro '%1'"_L1.arg(cmdStr));
2064 ++m_position;
2065 }
2066 }
2067 } else if (m_input[m_position].isSpace()) {
2068 skipAllSpaces();
2069 } else if (m_input[m_position].unicode() == '\\') {
2070 // allow escaping a backslash
2071 m_input.remove(m_position--, 1);
2072 --m_inputLength;
2073 }
2074 return false;
2075}
2076
2077void DocParser::expandMacro(const QString &def, const QStringList &args)
2078{
2079 if (args.isEmpty()) {
2080 appendAtom(Atom(Atom::RawString, def));
2081 } else {
2082 QString rawString;
2083
2084 for (int j = 0; j < def.size(); ++j) {
2085 if (int paramNo = def[j].unicode(); paramNo >= 1 && paramNo <= args.length()) {
2086 if (!rawString.isEmpty()) {
2087 appendAtom(Atom(Atom::RawString, rawString));
2088 rawString.clear();
2089 }
2090 appendAtom(Atom(Atom::String, args[paramNo - 1]));
2091 } else {
2092 rawString += def[j];
2093 }
2094 }
2095 if (!rawString.isEmpty())
2096 appendAtom(Atom(Atom::RawString, rawString));
2097 }
2098}
2099
2100QString DocParser::expandMacroToString(const QString &name, const Macro &macro)
2101{
2102 const QString &def{macro.m_defaultDef};
2103 QString rawString;
2104
2105 if (macro.numParams == 0) {
2106 rawString = macro.m_defaultDef;
2107 } else {
2108 QStringList args{getMacroArguments(name, macro)};
2109
2110 for (int j = 0; j < def.size(); ++j) {
2111 int paramNo = def[j].unicode();
2112 rawString += (paramNo >= 1 && paramNo <= args.length()) ? args[paramNo - 1] : def[j];
2113 }
2114 }
2115 QString matchExpr{macro.m_otherDefs.value("match")};
2116 if (matchExpr.isEmpty())
2117 return rawString;
2118
2119 QString result;
2120 QRegularExpression re(matchExpr);
2121 int capStart = (re.captureCount() > 0) ? 1 : 0;
2122 qsizetype i = 0;
2123 QRegularExpressionMatch match;
2124 while ((match = re.match(rawString, i)).hasMatch()) {
2125 for (int c = capStart; c <= re.captureCount(); ++c)
2126 result += match.captured(c);
2127 i = match.capturedEnd();
2128 }
2129
2130 return result;
2131}
2132
2133Doc::Sections DocParser::getSectioningUnit()
2134{
2135 QString name = getOptionalArgument();
2136
2137 if (name == "section1") {
2138 return Doc::Section1;
2139 } else if (name == "section2") {
2140 return Doc::Section2;
2141 } else if (name == "section3") {
2142 return Doc::Section3;
2143 } else if (name == "section4") {
2144 return Doc::Section4;
2145 } else if (name.isEmpty()) {
2146 return Doc::NoSection;
2147 } else {
2148 location().warning(QStringLiteral("Invalid section '%1'").arg(name));
2149 return Doc::NoSection;
2150 }
2151}
2152
2153/*!
2154 Gets an argument that is enclosed in braces and returns it
2155 without the enclosing braces. On entry, the current character
2156 is the left brace. On exit, the current character is the one
2157 that comes after the right brace.
2158
2159 If \a options is ArgumentParsingOptions::Verbatim, no macro
2160 expansion is performed, nor is the returned string stripped
2161 of extra whitespace.
2162 */
2163QString DocParser::getBracedArgument(ArgumentParsingOptions options)
2164{
2165 QString arg;
2166 int delimDepth = 0;
2167 if (m_position < m_input.size() && m_input[m_position] == '{') {
2168 ++m_position;
2169 while (m_position < m_input.size() && delimDepth >= 0) {
2170 switch (m_input[m_position].unicode()) {
2171 case '{':
2172 ++delimDepth;
2173 arg += QLatin1Char('{');
2174 ++m_position;
2175 break;
2176 case '}':
2177 --delimDepth;
2178 if (delimDepth >= 0)
2179 arg += QLatin1Char('}');
2180 ++m_position;
2181 break;
2182 case '\\':
2183 if (!expandMacro(options))
2184 arg += m_input[m_position++];
2185 break;
2186 default:
2187 if (m_input[m_position].isSpace() && options != ArgumentParsingOptions::Verbatim)
2188 arg += QChar(' ');
2189 else
2190 arg += m_input[m_position];
2191 ++m_position;
2192 }
2193 }
2194 if (delimDepth > 0)
2195 location().warning(QStringLiteral("Missing '}'"));
2196 }
2197 m_endPosition = m_position;
2198 return arg;
2199}
2200
2201/*!
2202 Parses and returns an argument for a command, using
2203 specific parsing \a options.
2204
2205 Typically, an argument ends at the next white-space. However,
2206 braces can be used to group words:
2207
2208 {a few words}
2209
2210 Also, opening and closing parentheses have to match. Thus,
2211
2212 printf("%d\n", x)
2213
2214 is an argument too, although it contains spaces. Finally,
2215 trailing punctuation is not included in an argument, nor is 's.
2216*/
2217QString DocParser::getArgument(ArgumentParsingOptions options)
2218{
2219 skipSpacesOrOneEndl();
2220
2221 int delimDepth = 0;
2222 qsizetype startPos = m_position;
2223 QString arg = getBracedArgument(options);
2224 if (arg.isEmpty()) {
2225 while ((m_position < m_input.size())
2226 && ((delimDepth > 0) || ((delimDepth == 0) && !m_input[m_position].isSpace()))) {
2227 switch (m_input[m_position].unicode()) {
2228 case '(':
2229 case '[':
2230 case '{':
2231 ++delimDepth;
2232 arg += m_input[m_position];
2233 ++m_position;
2234 break;
2235 case ')':
2236 case ']':
2237 case '}':
2238 --delimDepth;
2239 if (m_position == startPos || delimDepth >= 0) {
2240 arg += m_input[m_position];
2241 ++m_position;
2242 }
2243 break;
2244 case '\\':
2245 if (!expandMacro(options))
2246 arg += m_input[m_position++];
2247 break;
2248 default:
2249 arg += m_input[m_position];
2250 ++m_position;
2251 }
2252 }
2253 m_endPosition = m_position;
2254 if ((arg.size() > 1) && (QString(".,:;!?").indexOf(m_input[m_position - 1]) != -1)
2255 && !arg.endsWith("...")) {
2256 arg.truncate(arg.size() - 1);
2257 --m_position;
2258 }
2259 if (arg.size() > 2 && m_input.mid(m_position - 2, 2) == "'s") {
2260 arg.truncate(arg.size() - 2);
2261 m_position -= 2;
2262 }
2263 }
2264 return arg.simplified();
2265}
2266
2267/*!
2268 Gets an argument that is enclosed in brackets and returns it
2269 without the enclosing brackets. On entry, the current character
2270 is the left bracket. On exit, the current character is the one
2271 that comes after the right bracket.
2272 */
2273QString DocParser::getBracketedArgument()
2274{
2275 QString arg;
2276 int delimDepth = 0;
2277 skipSpacesOrOneEndl();
2278 if (m_position < m_input.size() && m_input[m_position] == '[') {
2279 ++m_position;
2280 while (m_position < m_input.size() && delimDepth >= 0) {
2281 switch (m_input[m_position].unicode()) {
2282 case '[':
2283 ++delimDepth;
2284 arg += QLatin1Char('[');
2285 ++m_position;
2286 break;
2287 case ']':
2288 --delimDepth;
2289 if (delimDepth >= 0)
2290 arg += QLatin1Char(']');
2291 ++m_position;
2292 break;
2293 case '\\':
2294 arg += m_input[m_position];
2295 ++m_position;
2296 break;
2297 default:
2298 arg += m_input[m_position];
2299 ++m_position;
2300 }
2301 }
2302 if (delimDepth > 0)
2303 location().warning(QStringLiteral("Missing ']'"));
2304 }
2305 return arg;
2306}
2307
2308
2309/*!
2310 Returns the list of arguments passed to a \a macro with name \a name.
2311
2312 If a macro takes more than a single argument, they are expected to be
2313 wrapped in braces.
2314*/
2315QStringList DocParser::getMacroArguments(const QString &name, const Macro &macro)
2316{
2317 QStringList args;
2318 for (int i = 0; i < macro.numParams; ++i) {
2319 if (macro.numParams == 1 || isLeftBraceAhead()) {
2320 args << getArgument(ArgumentParsingOptions::MacroArguments);
2321 } else {
2322 location().warning(QStringLiteral("Macro '\\%1' invoked with too few"
2323 " arguments (expected %2, got %3)")
2324 .arg(name)
2325 .arg(macro.numParams)
2326 .arg(i));
2327 break;
2328 }
2329 }
2330 return args;
2331}
2332
2333QString DocParser::getOptionalArgument()
2334{
2335 skipSpacesOrOneEndl();
2336 if (m_position + 1 < m_input.size() && m_input[m_position] == '\\'
2337 && m_input[m_position + 1].isLetterOrNumber()) {
2338 return QString();
2339 } else {
2340 return getArgument();
2341 }
2342}
2343
2344/*!
2345 \brief Create a string that may optionally span multiple lines as one line.
2346
2347 Process a block of text that may span multiple lines using trailing
2348 backslashes (`\`) as line continuation character. Trailing backslashes and
2349 any newline character that follow them are removed.
2350
2351 Returns a string as if it was one continuous line of text. If trailing
2352 backslashes are removed, the method returns a "simplified" QString, which
2353 means any sequence of internal whitespace is replaced with a single space.
2354
2355 Whitespace at the start and end is always removed from the returned string.
2356
2357 \sa QString::simplified(), QString::trimmed().
2358 */
2359QString DocParser::getRestOfLine()
2360{
2361 auto lineHasTrailingBackslash = [this](bool trailingBackslash) -> bool {
2362 while (m_position < m_inputLength && m_input[m_position] != '\n') {
2363 if (m_input[m_position] == '\\' && !trailingBackslash) {
2364 trailingBackslash = true;
2365 ++m_position;
2366 skipSpacesOnLine();
2367 } else {
2368 trailingBackslash = false;
2369 ++m_position;
2370 }
2371 }
2372 return trailingBackslash;
2373 };
2374
2375 QString rest_of_line;
2376 skipSpacesOnLine();
2377 bool trailing_backslash{ false };
2378 bool return_simplified_string{ false };
2379
2380 for (qsizetype start_position = m_position; m_position < m_inputLength; ++m_position) {
2381 trailing_backslash = lineHasTrailingBackslash(trailing_backslash);
2382
2383 if (!rest_of_line.isEmpty())
2384 rest_of_line += QLatin1Char(' ');
2385 rest_of_line += m_input.sliced(start_position, m_position - start_position);
2386
2387 if (trailing_backslash) {
2388 rest_of_line.truncate(rest_of_line.lastIndexOf('\\'));
2389 return_simplified_string = true;
2390 }
2391
2392 if (m_position < m_inputLength)
2393 ++m_position;
2394
2395 if (!trailing_backslash)
2396 break;
2397 start_position = m_position;
2398 }
2399
2400 if (return_simplified_string)
2401 return rest_of_line.simplified();
2402
2403 return rest_of_line.trimmed();
2404}
2405
2406/*!
2407 The metacommand argument is normally the remaining text to
2408 the right of the metacommand itself. The extra blanks are
2409 stripped and the argument string is returned.
2410 */
2411QString DocParser::getMetaCommandArgument(const QString &cmdStr)
2412{
2413 skipSpacesOnLine();
2414
2415 qsizetype begin = m_position;
2416 int parenDepth = 0;
2417
2418 while (m_position < m_input.size() && (m_input[m_position] != '\n' || parenDepth > 0)) {
2419 if (m_input.at(m_position) == '(')
2420 ++parenDepth;
2421 else if (m_input.at(m_position) == ')')
2422 --parenDepth;
2423 else if (m_input.at(m_position) == '\\' && expandMacro(ArgumentParsingOptions::Default))
2424 continue;
2425 ++m_position;
2426 }
2427 if (m_position == m_input.size() && parenDepth > 0) {
2428 m_position = begin;
2429 location().warning(QStringLiteral("Unbalanced parentheses in '%1'").arg(cmdStr));
2430 }
2431
2432 QString t = m_input.mid(begin, m_position - begin).simplified();
2433 skipSpacesOnLine();
2434 return t;
2435}
2436
2437QString DocParser::getUntilEnd(int cmd)
2438{
2439 int endCmd = endCmdFor(cmd);
2440 QRegularExpression rx("\\\\" + cmdName(endCmd) + "\\b");
2441 QString t;
2442 auto match = rx.match(m_input, m_position);
2443
2444 if (!match.hasMatch()) {
2445 location().warning(QStringLiteral("Missing '\\%1'").arg(cmdName(endCmd)));
2446 m_position = m_input.size();
2447 } else {
2448 qsizetype end = match.capturedStart();
2449 t = m_input.mid(m_position, end - m_position);
2450 m_position = match.capturedEnd();
2451 }
2452 return t;
2453}
2454
2455void DocParser::expandArgumentsInString(QString &str, const QStringList &args)
2456{
2457 if (args.isEmpty())
2458 return;
2459
2460 qsizetype paramNo;
2461 qsizetype j = 0;
2462 while (j < str.size()) {
2463 if (str[j] == '\\' && j < str.size() - 1 && (paramNo = str[j + 1].digitValue()) >= 1
2464 && paramNo <= args.size()) {
2465 const QString &r = args[paramNo - 1];
2466 str.replace(j, 2, r);
2467 j += qMin(1, r.size());
2468 } else {
2469 ++j;
2470 }
2471 }
2472}
2473
2474/*!
2475 Returns the marked-up code following the code-quoting command \a cmd, expanding
2476 any arguments passed in \a argStr.
2477
2478 Uses the \a marker to mark up the code. If it's \c nullptr, resolve the marker
2479 based on the topic and the quoted code itself.
2480*/
2481QString DocParser::getCode(int cmd, CodeMarker *marker, const QString &argStr)
2482{
2483 QString code = untabifyEtc(getUntilEnd(cmd));
2484 expandArgumentsInString(code, argStr.split(" ", Qt::SkipEmptyParts));
2485
2486 int indent = indentLevel(code);
2487 code = dedent(indent, code);
2488
2489 // If we're in a QML topic, check if the QML marker recognizes the code
2490 if (!marker && !m_private->m_topics.isEmpty()
2491 && m_private->m_topics[0].m_topic.startsWith("qml")) {
2492 auto qmlMarker = CodeMarker::markerForLanguage("QML");
2493 marker = (qmlMarker && qmlMarker->recognizeCode(code)) ? qmlMarker : nullptr;
2494 }
2495 if (marker == nullptr)
2496 marker = CodeMarker::markerForCode(code);
2497 return marker->markedUpCode(code, nullptr, location());
2498}
2499
2500bool DocParser::isBlankLine()
2501{
2502 qsizetype i = m_position;
2503
2504 while (i < m_inputLength && m_input[i].isSpace()) {
2505 if (m_input[i] == '\n')
2506 return true;
2507 ++i;
2508 }
2509 return false;
2510}
2511
2512bool DocParser::isLeftBraceAhead()
2513{
2514 int numEndl = 0;
2515 qsizetype i = m_position;
2516
2517 while (i < m_inputLength && m_input[i].isSpace() && numEndl < 2) {
2518 // ### bug with '\\'
2519 if (m_input[i] == '\n')
2520 numEndl++;
2521 ++i;
2522 }
2523 return numEndl < 2 && i < m_inputLength && m_input[i] == '{';
2524}
2525
2526bool DocParser::isLeftBracketAhead()
2527{
2528 int numEndl = 0;
2529 qsizetype i = m_position;
2530
2531 while (i < m_inputLength && m_input[i].isSpace() && numEndl < 2) {
2532 // ### bug with '\\'
2533 if (m_input[i] == '\n')
2534 numEndl++;
2535 ++i;
2536 }
2537 return numEndl < 2 && i < m_inputLength && m_input[i] == '[';
2538}
2539
2540/*!
2541 Skips to the next non-space character or EOL.
2542 */
2543void DocParser::skipSpacesOnLine()
2544{
2545 while ((m_position < m_input.size()) && m_input[m_position].isSpace()
2546 && (m_input[m_position].unicode() != '\n'))
2547 ++m_position;
2548}
2549
2550/*!
2551 Skips spaces and one EOL.
2552 */
2553void DocParser::skipSpacesOrOneEndl()
2554{
2555 qsizetype firstEndl = -1;
2556 while (m_position < m_input.size() && m_input[m_position].isSpace()) {
2557 QChar ch = m_input[m_position];
2558 if (ch == '\n') {
2559 if (firstEndl == -1) {
2560 firstEndl = m_position;
2561 } else {
2562 m_position = firstEndl;
2563 break;
2564 }
2565 }
2566 ++m_position;
2567 }
2568}
2569
2570void DocParser::skipAllSpaces()
2571{
2572 while (m_position < m_inputLength && m_input[m_position].isSpace())
2573 ++m_position;
2574}
2575
2576void DocParser::skipToNextPreprocessorCommand()
2577{
2578 QRegularExpression rx("\\\\‍(?:" + cmdName(CMD_IF) + QLatin1Char('|') + cmdName(CMD_ELSE)
2579 + QLatin1Char('|') + cmdName(CMD_ENDIF) + ")\\b");
2580 auto match = rx.match(m_input, m_position + 1); // ### + 1 necessary?
2581
2582 if (!match.hasMatch())
2583 m_position = m_input.size();
2584 else
2585 m_position = match.capturedStart();
2586}
2587
2589{
2590 switch (cmd) {
2591 case CMD_BADCODE:
2592 return CMD_ENDCODE;
2593 case CMD_CODE:
2594 return CMD_ENDCODE;
2595 case CMD_COMPARESWITH:
2596 return CMD_ENDCOMPARESWITH;
2597 case CMD_DETAILS:
2598 return CMD_ENDDETAILS;
2599 case CMD_DIV:
2600 return CMD_ENDDIV;
2601 case CMD_QML:
2602 return CMD_ENDQML;
2603 case CMD_FOOTNOTE:
2604 return CMD_ENDFOOTNOTE;
2605 case CMD_LEGALESE:
2606 return CMD_ENDLEGALESE;
2607 case CMD_LINK:
2608 return CMD_ENDLINK;
2609 case CMD_LIST:
2610 return CMD_ENDLIST;
2611 case CMD_OMIT:
2612 return CMD_ENDOMIT;
2613 case CMD_QUOTATION:
2614 return CMD_ENDQUOTATION;
2615 case CMD_RAW:
2616 return CMD_ENDRAW;
2617 case CMD_SECTION1:
2618 return CMD_ENDSECTION1;
2619 case CMD_SECTION2:
2620 return CMD_ENDSECTION2;
2621 case CMD_SECTION3:
2622 return CMD_ENDSECTION3;
2623 case CMD_SECTION4:
2624 return CMD_ENDSECTION4;
2625 case CMD_SIDEBAR:
2626 return CMD_ENDSIDEBAR;
2627 case CMD_TABLE:
2628 return CMD_ENDTABLE;
2629 default:
2630 return cmd;
2631 }
2632}
2633
2634QString DocParser::cmdName(int cmd)
2635{
2636 return cmds[cmd].name;
2637}
2638
2639QString DocParser::endCmdName(int cmd)
2640{
2641 return cmdName(endCmdFor(cmd));
2642}
2643
2644QString DocParser::untabifyEtc(const QString &str)
2645{
2646 QString result;
2647 result.reserve(str.size());
2648 int column = 0;
2649
2650 for (const auto &character : str) {
2651 if (character == QLatin1Char('\r'))
2652 continue;
2653 if (character == QLatin1Char('\t')) {
2654 result += &" "[column % s_tabSize];
2655 column = ((column / s_tabSize) + 1) * s_tabSize;
2656 continue;
2657 }
2658 if (character == QLatin1Char('\n')) {
2659 while (result.endsWith(QLatin1Char(' ')))
2660 result.chop(1);
2661 result += character;
2662 column = 0;
2663 continue;
2664 }
2665 result += character;
2666 ++column;
2667 }
2668
2669 while (result.endsWith("\n\n"))
2670 result.truncate(result.size() - 1);
2671 while (result.startsWith(QLatin1Char('\n')))
2672 result = result.mid(1);
2673
2674 return result;
2675}
2676
2677int DocParser::indentLevel(const QString &str)
2678{
2679 int minIndent = INT_MAX;
2680 int column = 0;
2681
2682 for (const auto &character : str) {
2683 if (character == '\n') {
2684 column = 0;
2685 } else {
2686 if (character != ' ' && column < minIndent)
2687 minIndent = column;
2688 ++column;
2689 }
2690 }
2691 return minIndent;
2692}
2693
2694QString DocParser::dedent(int level, const QString &str)
2695{
2696 if (level == 0)
2697 return str;
2698
2699 QString result;
2700 int column = 0;
2701
2702 for (const auto &character : str) {
2703 if (character == QLatin1Char('\n')) {
2704 result += '\n';
2705 column = 0;
2706 } else {
2707 if (column >= level)
2708 result += character;
2709 ++column;
2710 }
2711 }
2712 return result;
2713}
2714
2715/*!
2716 Returns \c true if \a atom represents a code snippet.
2717 */
2718bool DocParser::isCode(const Atom *atom)
2719{
2720 Atom::AtomType type = atom->type();
2721 return (type == Atom::Code || type == Atom::Qml);
2722}
2723
2724/*!
2725 Returns \c true if \a atom represents quoting information.
2726 */
2727bool DocParser::isQuote(const Atom *atom)
2728{
2729 Atom::AtomType type = atom->type();
2730 return (type == Atom::CodeQuoteArgument || type == Atom::CodeQuoteCommand
2731 || type == Atom::SnippetCommand || type == Atom::SnippetIdentifier
2732 || type == Atom::SnippetLocation);
2733}
2734
2735/*!
2736 \internal
2737 Processes the arguments passed to the \\compareswith block command.
2738 The arguments are stored as text within the first atom of the block
2739 (Atom::ComparesLeft).
2740
2741 Extracts the comparison category and the list of types, and stores
2742 the information into a map accessed via \a priv.
2743*/
2744static void processComparesWithCommand(DocPrivate *priv, const Location &location)
2745{
2746 static auto take_while = [](QStringView input, auto predicate) {
2747 QStringView::size_type end{0};
2748
2749 while (end < input.size() && std::invoke(predicate, input[end]))
2750 ++end;
2751
2752 return std::make_tuple(input.sliced(0, end), input.sliced(end));
2753 };
2754
2755 static auto peek = [](QStringView input, QChar c) {
2756 return !input.empty() && input.first() == c;
2757 };
2758
2759 static auto skip_one = [](QStringView input) {
2760 if (input.empty()) return std::make_tuple(QStringView{}, input);
2761 else return std::make_tuple(input.sliced(0, 1), input.sliced(1));
2762 };
2763
2764 static auto enclosed = [](QStringView input, QChar open, QChar close) {
2765 if (!peek(input, open)) return std::make_tuple(QStringView{}, input);
2766
2767 auto [opened, without_open] = skip_one(input);
2768 auto [parsed, remaining] = take_while(without_open, [close](QChar c){ return c != close; });
2769
2770 if (remaining.empty()) return std::make_tuple(QStringView{}, input);
2771
2772 auto [closed, without_close] = skip_one(remaining);
2773
2774 return std::make_tuple(parsed.trimmed(), without_close);
2775 };
2776
2777 static auto one_of = [](auto first, auto second) {
2778 return [first, second](QStringView input) {
2779 auto [parsed, remaining] = std::invoke(first, input);
2780
2781 if (parsed.empty()) return std::invoke(second, input);
2782 else return std::make_tuple(parsed, remaining);
2783 };
2784 };
2785
2786 static auto collect = [](QStringView input, auto parser) {
2787 QStringList collected{};
2788
2789 while (true) {
2790 auto [parsed, remaining] = std::invoke(parser, input);
2791
2792 if (parsed.empty()) break;
2793 collected.append(parsed.toString());
2794
2795 input = remaining;
2796 };
2797
2798 return collected;
2799 };
2800
2801 static auto spaces = [](QStringView input) {
2802 return take_while(input, [](QChar c){ return c.isSpace(); });
2803 };
2804
2805 static auto word = [](QStringView input) {
2806 return take_while(input, [](QChar c){ return !c.isSpace(); });
2807 };
2808
2809 static auto parse_argument = [](QStringView input) {
2810 auto [_, without_spaces] = spaces(input);
2811
2812 return one_of(
2813 [](QStringView input){ return enclosed(input, '{', '}'); },
2814 word
2815 )(without_spaces);
2816 };
2817
2818 const QString cmd{DocParser::cmdName(CMD_COMPARESWITH)};
2820
2821 auto *atom = text.firstAtom();
2822 QStringList segments = collect(atom->string(), parse_argument);
2823
2824 QString categoryString;
2825 if (!segments.isEmpty())
2826 categoryString = segments.takeFirst();
2827 auto category = comparisonCategoryFromString(categoryString.toStdString());
2828
2829 if (category == ComparisonCategory::None) {
2830 location.warning(u"Invalid argument to \\%1 command: `%2`"_s.arg(cmd, categoryString),
2831 u"Valid arguments are `strong`, `weak`, `partial`, or `equality`."_s);
2832 return;
2833 }
2834
2835 if (segments.isEmpty()) {
2836 location.warning(u"Missing argument to \\%1 command."_s.arg(cmd),
2837 u"Provide at least one type name, or a list of types separated by spaces."_s);
2838 return;
2839 }
2840
2841 // Store cleaned-up type names back into the atom
2842 segments.removeDuplicates();
2843 atom->setString(segments.join(QLatin1Char(';')));
2844
2845 // Add an entry to meta-command map for error handling in CppCodeParser
2846 priv->m_metaCommandMap[cmd].append(ArgPair(categoryString, atom->string()));
2847 priv->m_metacommandsUsed.insert(cmd);
2848
2850 const auto end{priv->extra->m_comparesWithMap.cend()};
2851 priv->extra->m_comparesWithMap.insert(end, category, text);
2852}
2853
2854QT_END_NAMESPACE
#define ATOM_FORMATTING_TELETYPE
Definition atom.h:207
#define ATOM_FORMATTING_UNDERLINE
Definition atom.h:210
#define ATOM_FORMATTING_SPAN
Definition atom.h:204
#define ATOM_FORMATTING_SUBSCRIPT
Definition atom.h:205
#define ATOM_FORMATTING_BOLD
Definition atom.h:199
#define ATOM_FORMATTING_TRADEMARK
Definition atom.h:208
#define ATOM_LIST_VALUE
Definition atom.h:214
#define ATOM_FORMATTING_ITALIC
Definition atom.h:201
#define ATOM_FORMATTING_LINK
Definition atom.h:202
#define ATOM_FORMATTING_SUPERSCRIPT
Definition atom.h:206
#define ATOM_FORMATTING_INDEX
Definition atom.h:200
#define ATOM_FORMATTING_UICONTROL
Definition atom.h:209
#define ATOM_FORMATTING_PARAMETER
Definition atom.h:203
The Atom class is the fundamental unit for representing documents internally.
Definition atom.h:18
AtomType type() const
Return the type of this atom.
Definition atom.h:149
AtomType
\value AnnotatedList \value AutoLink \value BaseName \value BriefLeft \value BriefRight \value C \val...
Definition atom.h:20
@ ListTagLeft
Definition atom.h:64
@ TableRight
Definition atom.h:94
@ DivRight
Definition atom.h:39
@ CodeQuoteArgument
Definition atom.h:32
@ SidebarLeft
Definition atom.h:84
@ Keyword
Definition atom.h:56
@ TableHeaderRight
Definition atom.h:96
@ FormatElse
Definition atom.h:44
@ TableRowRight
Definition atom.h:98
@ FootnoteRight
Definition atom.h:43
@ SnippetCommand
Definition atom.h:89
@ TableRowLeft
Definition atom.h:97
@ Nop
Definition atom.h:71
@ LegaleseRight
Definition atom.h:58
@ ListTagRight
Definition atom.h:65
@ SinceTagRight
Definition atom.h:88
@ DetailsLeft
Definition atom.h:36
@ RawString
Definition atom.h:79
@ Target
Definition atom.h:102
@ SectionHeadingLeft
Definition atom.h:82
@ ListItemRight
Definition atom.h:67
@ TableItemRight
Definition atom.h:100
@ ListItemLeft
Definition atom.h:66
@ Code
Definition atom.h:30
@ String
Definition atom.h:92
@ ListLeft
Definition atom.h:62
@ CodeQuoteCommand
Definition atom.h:33
@ LegaleseLeft
Definition atom.h:57
@ ListRight
Definition atom.h:68
@ C
Definition atom.h:27
@ ParaRight
Definition atom.h:75
@ Qml
Definition atom.h:76
@ FormattingLeft
Definition atom.h:47
@ FormattingRight
Definition atom.h:48
@ Link
Definition atom.h:60
@ FormatEndif
Definition atom.h:45
@ SinceTagLeft
Definition atom.h:87
@ BR
Definition atom.h:24
@ DetailsRight
Definition atom.h:37
@ FootnoteLeft
Definition atom.h:42
@ AutoLink
Definition atom.h:22
@ SnippetLocation
Definition atom.h:91
@ TableHeaderLeft
Definition atom.h:95
@ ComparesRight
Definition atom.h:35
@ QuotationLeft
Definition atom.h:77
@ HR
Definition atom.h:50
@ DivLeft
Definition atom.h:38
@ QuotationRight
Definition atom.h:78
@ ParaLeft
Definition atom.h:74
@ TableOfContents
Definition atom.h:101
@ ComparesLeft
Definition atom.h:34
@ FormatIf
Definition atom.h:46
@ SnippetIdentifier
Definition atom.h:90
@ SidebarRight
Definition atom.h:85
@ UnknownCommand
Definition atom.h:106
void chopString()
\also string()
Definition atom.h:139
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:84
static QStringList s_ignoreWords
Definition docparser.h:43
void parse(const QString &source, DocPrivate *docPrivate, const QSet< QString > &metaCommandSet, const QSet< QString > &possibleTopics)
Parse the source string to build a Text data structure in docPrivate.
static bool s_quoting
Definition docparser.h:44
static int endCmdFor(int cmd)
static void initialize(const Config &config, FileResolver &file_resolver)
static int s_tabSize
Definition docparser.h:42
DocPrivateExtra * extra
Definition docprivate.h:68
void constructExtra()
Text m_text
Definition docprivate.h:61
bool m_hasLegalese
Definition docprivate.h:71
void addAlso(const Text &also)
TopicList m_topics
Definition docprivate.h:69
Definition doc.h:31
Sections
Definition doc.h:34
@ NoSection
Definition doc.h:35
@ Section3
Definition doc.h:38
@ Section1
Definition doc.h:36
@ Section2
Definition doc.h:37
@ Section4
Definition doc.h:39
Encapsulate the logic that QDoc uses to find files whose path is provided by the user and that are re...
The Location class provides a way to mark a location in a file.
Definition location.h:15
int depth() const
Definition location.h:39
Definition text.h:12
Text splitAtFirst(Atom::AtomType start)
Splits the current Text from start to end into a new Text object.
Definition text.cpp:313
void stripFirstAtom()
Definition text.cpp:91
Atom * firstAtom()
Definition text.h:21
void stripLastAtom()
Definition text.cpp:102
Atom * lastAtom()
Definition text.h:22
#define CONFIG_TABSIZE
Definition config.h:386
#define CONFIG_IGNOREWORDS
Definition config.h:352
#define CONFIG_QUOTINGINFORMATION
Definition config.h:376
std::pair< QString, QString > ArgPair
Definition doc.h:26
static void warnAboutEmptyOrPreexistingTarget(const Location &location, const QString &duplicateDefinition, const QString &cmdString, const QString &previousDefinition)
bool is_formatting_command
int no
static void processComparesWithCommand(DocPrivate *priv, const Location &location)
const char * name
@ CMD_OVERLOAD
Definition docparser.cpp:87
@ CMD_UICONTROL
@ CMD_HEADER
Definition docparser.cpp:66
@ CMD_QUOTATION
Definition docparser.cpp:91
@ CMD_LEGALESE
Definition docparser.cpp:78
@ CMD_DOTS
Definition docparser.cpp:42
@ CMD_OMITVALUE
Definition docparser.cpp:86
@ CMD_ENDSECTION2
Definition docparser.cpp:59
@ CMD_INDEX
Definition docparser.cpp:74
@ CMD_INCLUDE
Definition docparser.cpp:72
@ CMD_PRINTUNTIL
Definition docparser.cpp:90
@ CMD_QUOTEFILE
Definition docparser.cpp:92
@ CMD_FOOTNOTE
Definition docparser.cpp:64
@ CMD_ENDDETAILS
Definition docparser.cpp:47
@ CMD_SNIPPET
@ CMD_TABLEOFCONTENTS
@ CMD_GENERATELIST
Definition docparser.cpp:65
@ CMD_SECTION2
Definition docparser.cpp:98
@ CMD_UNDERLINE
@ CMD_UNICODE
@ CMD_ENDLEGALESE
Definition docparser.cpp:51
@ CMD_TT
@ CMD_DIV
Definition docparser.cpp:41
@ CMD_I
Definition docparser.cpp:68
@ CMD_META
Definition docparser.cpp:82
@ CMD_SKIPLINE
@ CMD_BADCODE
Definition docparser.cpp:31
@ CMD_ENDOMIT
Definition docparser.cpp:55
@ CMD_CPP
@ CMD_TARGET
@ CMD_A
Definition docparser.cpp:28
@ CMD_SIDEBAR
@ CMD_HR
Definition docparser.cpp:67
@ CMD_INPUT
Definition docparser.cpp:75
@ CMD_ENDMAPREF
Definition docparser.cpp:54
@ CMD_SKIPUNTIL
@ CMD_TM
@ CMD_CODE
Definition docparser.cpp:37
@ CMD_COMPARESWITH
Definition docparser.cpp:39
@ CMD_SUP
@ CMD_SKIPTO
@ CMD_ENDQML
@ CMD_ENDLINK
Definition docparser.cpp:52
@ CMD_ENDCPPTEXT
@ CMD_LIST
Definition docparser.cpp:81
@ CMD_ENDSECTION3
Definition docparser.cpp:60
@ CMD_SECTION4
@ CMD_ENDCPP
@ CMD_QML
@ CMD_SINCELIST
@ CMD_ENDIF
Definition docparser.cpp:50
@ CMD_LINK
Definition docparser.cpp:80
@ CMD_CPPTEXT
@ CMD_PRINTLINE
Definition docparser.cpp:88
@ CMD_RAW
Definition docparser.cpp:94
@ CMD_ROW
Definition docparser.cpp:95
@ CMD_ENDTABLE
Definition docparser.cpp:63
@ CMD_SUB
@ CMD_NOTE
Definition docparser.cpp:83
@ CMD_DETAILS
Definition docparser.cpp:40
@ CMD_CAPTION
Definition docparser.cpp:36
@ CMD_ENDCODE
Definition docparser.cpp:45
@ CMD_KEYWORD
Definition docparser.cpp:76
@ CMD_B
Definition docparser.cpp:30
@ CMD_ELSE
Definition docparser.cpp:44
@ CMD_ENDRAW
Definition docparser.cpp:57
@ CMD_ENDDIV
Definition docparser.cpp:48
@ CMD_ANNOTATEDLIST
Definition docparser.cpp:29
@ CMD_E
Definition docparser.cpp:43
@ CMD_SECTION3
Definition docparser.cpp:99
@ CMD_WARNING
@ CMD_C
Definition docparser.cpp:35
@ NOT_A_CMD
@ CMD_SECTION1
Definition docparser.cpp:97
@ CMD_IMAGE
Definition docparser.cpp:70
@ CMD_ENDSECTION4
Definition docparser.cpp:61
@ CMD_BR
Definition docparser.cpp:33
@ CMD_INLINEIMAGE
Definition docparser.cpp:73
@ CMD_SA
Definition docparser.cpp:96
@ CMD_CODELINE
Definition docparser.cpp:38
@ CMD_ENDCOMPARESWITH
Definition docparser.cpp:46
@ CMD_BOLD
Definition docparser.cpp:32
@ CMD_ENDQUOTATION
Definition docparser.cpp:56
@ CMD_L
Definition docparser.cpp:77
@ CMD_IMPORTANT
Definition docparser.cpp:71
@ CMD_ENDSECTION1
Definition docparser.cpp:58
@ CMD_ENDFOOTNOTE
Definition docparser.cpp:49
@ CMD_ENDLIST
Definition docparser.cpp:53
@ CMD_QUOTEFROMFILE
Definition docparser.cpp:93
@ CMD_LI
Definition docparser.cpp:79
@ CMD_IF
Definition docparser.cpp:69
@ CMD_ENDSIDEBAR
Definition docparser.cpp:62
@ CMD_PRINTTO
Definition docparser.cpp:89
@ CMD_O
Definition docparser.cpp:84
@ CMD_BRIEF
Definition docparser.cpp:34
@ CMD_OMIT
Definition docparser.cpp:85
@ CMD_VALUE
@ CMD_SPAN
@ CMD_TABLE
static QString cleanLink(const QString &link)
Combined button and popup list for selecting options.
QHash_QString_Macro macroHash
QHash_QString_int cmdHash
Simple structure used by the Doc and DocParser classes.
int numParams
Definition macro.h:22
Definition topic.h:9