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