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 // The "primary" flag is processed by the code parser, but we don't want it
1816 // passed to the generator as a target function name
1817 if (overloadArgument.trimmed() == "primary") {
1818 overloadArgument.clear(); // Use empty argument for default behavior
1819 }
1820
1821 m_private->m_metaCommandMap[cmd].append(ArgPair(overloadArgument, QString()));
1822}
1823
1824/*!
1825 \internal
1826 \brief Parses arguments to QDoc's see also command.
1827
1828 Parses space or comma separated arguments passed to the \\sa command.
1829 Multi-line input requires that the arguments are comma separated. Wrap
1830 arguments in curly braces for multi-word targets, and for scope resolution
1831 (for example, {QString::}{count()}).
1832
1833 This method updates the list of links for the See also section.
1834
1835 \sa {DocPrivate::}{addAlso()}, getArgument()
1836 */
1837void DocParser::parseAlso()
1838{
1839 auto line_comment = [this]() -> bool {
1840 skipSpacesOnLine();
1841 if (m_position + 2 > m_inputLength)
1842 return false;
1843 if (m_input[m_position].unicode() == '/') {
1844 if (m_input[m_position + 1].unicode() == '/') {
1845 if (m_input[m_position + 2].unicode() == '!') {
1846 return true;
1847 }
1848 }
1849 }
1850 return false;
1851 };
1852
1853 auto skip_everything_until_newline = [this]() -> void {
1854 while (m_position < m_inputLength && m_input[m_position] != '\n')
1855 ++m_position;
1856 };
1857
1858 leavePara();
1859 skipSpacesOnLine();
1860 while (m_position < m_inputLength && m_input[m_position] != '\n') {
1861 QString target;
1862 QString str;
1863 bool skipMe = false;
1864
1865 if (m_input[m_position] == '{') {
1866 target = getArgument();
1867 skipSpacesOnLine();
1868 if (m_position < m_inputLength && m_input[m_position] == '{') {
1869 str = getArgument();
1870
1871 // hack for C++ to support links like \l{QString::}{count()}
1872 if (target.endsWith("::"))
1873 target += str;
1874 } else {
1875 str = target;
1876 }
1877 } else {
1878 target = getArgument();
1879 str = cleanLink(target);
1880 if (target == QLatin1String("and") || target == QLatin1String("."))
1881 skipMe = true;
1882 }
1883
1884 if (!skipMe) {
1885 Text also;
1886 also << Atom(Atom::Link, target) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1888 m_private->addAlso(also);
1889 }
1890
1891 skipSpacesOnLine();
1892
1893 if (line_comment())
1894 skip_everything_until_newline();
1895
1896 if (m_position < m_inputLength && m_input[m_position] == ',') {
1897 m_position++;
1898 if (line_comment())
1899 skip_everything_until_newline();
1900 skipSpacesOrOneEndl();
1901 } else if (m_position >= m_inputLength || m_input[m_position] != '\n') {
1902 location().warning(QStringLiteral("Missing comma in '\\%1'").arg(cmdName(CMD_SA)));
1903 }
1904 }
1905}
1906
1907void DocParser::appendAtom(const Atom& atom) {
1908 m_private->m_text << atom;
1909}
1910
1911void DocParser::appendAtom(const LinkAtom& atom) {
1912 m_private->m_text << atom;
1913}
1914
1915void DocParser::appendChar(QChar ch)
1916{
1918 appendAtom(Atom(Atom::String));
1919 Atom *atom = m_private->m_text.lastAtom();
1920 if (ch == QLatin1Char(' ')) {
1921 if (!atom->string().endsWith(QLatin1Char(' ')))
1922 atom->appendChar(QLatin1Char(' '));
1923 } else
1924 atom->appendChar(ch);
1925}
1926
1927void DocParser::appendWord(const QString &word)
1928{
1929 if (m_private->m_text.lastAtom()->type() != Atom::String) {
1930 appendAtom(Atom(Atom::String, word));
1931 } else
1932 m_private->m_text.lastAtom()->concatenateString(word);
1933}
1934
1935void DocParser::appendToCode(const QString &markedCode)
1936{
1937 if (!isCode(m_lastAtom)) {
1938 appendAtom(Atom(Atom::Code));
1939 m_lastAtom = m_private->m_text.lastAtom();
1940 }
1941 m_lastAtom->concatenateString(markedCode);
1942}
1943
1944void DocParser::appendToCode(const QString &markedCode, Atom::AtomType defaultType)
1945{
1946 if (!isCode(m_lastAtom)) {
1947 appendAtom(Atom(defaultType, markedCode));
1948 m_lastAtom = m_private->m_text.lastAtom();
1949 } else {
1950 m_lastAtom->concatenateString(markedCode);
1951 }
1952}
1953
1954void DocParser::enterPara(Atom::AtomType leftType, Atom::AtomType rightType, const QString &string)
1955{
1956 if (m_paragraphState != OutsideParagraph)
1957 return;
1958
1962 leaveValueList();
1963 }
1964
1965 appendAtom(Atom(leftType, string));
1966 m_indexStartedParagraph = false;
1967 m_pendingParagraphLeftType = leftType;
1968 m_pendingParagraphRightType = rightType;
1969 m_pendingParagraphString = string;
1970 if (leftType == Atom::SectionHeadingLeft) {
1971 m_paragraphState = InSingleLineParagraph;
1972 } else {
1973 m_paragraphState = InMultiLineParagraph;
1974 }
1975 skipSpacesOrOneEndl();
1976}
1977
1978void DocParser::leavePara()
1979{
1980 if (m_paragraphState == OutsideParagraph)
1981 return;
1982
1983 if (!m_pendingFormats.isEmpty()) {
1984 location().warning(QStringLiteral("Missing '}'"));
1985 m_pendingFormats.clear();
1986 }
1987
1988 if (m_private->m_text.lastAtom()->type() == m_pendingParagraphLeftType) {
1989 m_private->m_text.stripLastAtom();
1990 } else {
1992 && m_private->m_text.lastAtom()->string().endsWith(QLatin1Char(' '))) {
1994 }
1995 appendAtom(Atom(m_pendingParagraphRightType, m_pendingParagraphString));
1996 }
1997 m_paragraphState = OutsideParagraph;
1998 m_indexStartedParagraph = false;
1999 m_pendingParagraphRightType = Atom::Nop;
2000 m_pendingParagraphString.clear();
2001}
2002
2003void DocParser::leaveValue()
2004{
2005 leavePara();
2006 if (m_openedLists.isEmpty()) {
2007 m_openedLists.push(OpenedList(OpenedList::Value));
2008 appendAtom(Atom(Atom::ListLeft, ATOM_LIST_VALUE));
2009 } else {
2010 if (m_private->m_text.lastAtom()->type() == Atom::Nop)
2011 m_private->m_text.stripLastAtom();
2012 appendAtom(Atom(Atom::ListItemRight, ATOM_LIST_VALUE));
2013 }
2014}
2015
2016void DocParser::leaveValueList()
2017{
2018 leavePara();
2019 if (!m_openedLists.isEmpty() && (m_openedLists.top().style() == OpenedList::Value)) {
2020 if (m_private->m_text.lastAtom()->type() == Atom::Nop)
2021 m_private->m_text.stripLastAtom();
2022 appendAtom(Atom(Atom::ListItemRight, ATOM_LIST_VALUE));
2023 appendAtom(Atom(Atom::ListRight, ATOM_LIST_VALUE));
2024 m_openedLists.pop();
2025 }
2026}
2027
2028void DocParser::leaveTableRow()
2029{
2030 if (m_inTableItem) {
2031 leavePara();
2032 appendAtom(Atom(Atom::TableItemRight));
2033 m_inTableItem = false;
2034 }
2035 if (m_inTableHeader) {
2036 appendAtom(Atom(Atom::TableHeaderRight));
2037 m_inTableHeader = false;
2038 }
2039 if (m_inTableRow) {
2040 appendAtom(Atom(Atom::TableRowRight));
2041 m_inTableRow = false;
2042 }
2043}
2044
2045void DocParser::quoteFromFile(const QString &filename)
2046{
2047 // KLUDGE: We dereference file_resolver as it is temporarily a pointer.
2048 // See the comment for file_resolver in the header files for more context.
2049 //
2050 // We spefically dereference it, instead of using the arrow
2051 // operator, to better represent that we do not consider this as
2052 // an actual pointer, as it should not be.
2053 //
2054 // Do note that we are considering it informally safe to
2055 // dereference the pointer, as we expect it to always hold a value
2056 // at this point, but actual enforcement of this appears nowhere
2057 // in the codebase.
2058 auto maybe_resolved_file{(*file_resolver).resolve(filename)};
2059 if (!maybe_resolved_file) {
2060 // TODO: [uncentralized-admonition][failed-resolve-file]
2061 // This warning is required in multiple places.
2062 // To ensure the consistency of the warning and avoid
2063 // duplicating code everywhere, provide a centralized effort
2064 // where the warning message can be generated (but not
2065 // issued).
2066 // The current format is based on what was used before, review
2067 // it when it is moved out.
2068 QString details = std::transform_reduce(
2069 (*file_resolver).get_search_directories().cbegin(),
2070 (*file_resolver).get_search_directories().cend(),
2071 u"Searched directories:"_s,
2072 std::plus(),
2073 [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
2074 );
2075
2076 location().warning(u"Cannot find file to quote from: %1"_s.arg(filename), details);
2077
2078 // REMARK: The following is duplicated from
2079 // Doc::quoteFromFile. If, for some reason (such as a file
2080 // that is inaccessible), the quoting fails but, previously,
2081 // the logic duplicated here was still run.
2082 // This is not true anymore as quoteFromFile does require a
2083 // resolved file to be run now.
2084 // It is not entirely clear if this is required for the
2085 // semantics of DocParser to be preserved, but for the sake of
2086 // avoiding premature breakages this was retained.
2087 // Do note that this should be considered temporary as the
2088 // quoter state, if any will be preserved, should not be
2089 // managed in such a spread and unlocal way.
2090 m_quoter.reset();
2091
2092 CodeMarker *marker = CodeMarker::markerForFileName(QString{});
2093 m_quoter.quoteFromFile(filename, QString{}, marker->markedUpCode(QString{}, nullptr, location()));
2094 } else Doc::quoteFromFile(location(), m_quoter, *maybe_resolved_file);
2095}
2096
2097/*!
2098 Expands a macro in-place in input.
2099
2100 Expects the current \e pos in the input to point to a backslash, and the macro to have a
2101 default definition. Format-specific macros are not expanded.
2102
2103 Behavior depends on \a options:
2104
2105 \value ArgumentParsingOptions::Default
2106 Default macro expansion; the string following the backslash
2107 must be a macro with a default definition.
2108 \value ArgumentParsingOptions::Verbatim
2109 The string following the backslash is rendered verbatim;
2110 No macro expansion is performed.
2111 \value ArgumentParsingOptions::MacroArguments
2112 Used for parsing argument(s) for a macro. Allows expanding
2113 macros, and also preserves a subset of commands (formatting
2114 commands) within the macro argument.
2115
2116 \note In addition to macros, a valid use for a backslash in an argument include
2117 escaping non-alnum characters, and splitting a single argument across multiple
2118 lines by escaping newlines. Escaping is also handled here.
2119
2120 Returns \c true on successful macro expansion.
2121 */
2122bool DocParser::expandMacro(ArgumentParsingOptions options)
2123{
2124 Q_ASSERT(m_input[m_position].unicode() == '\\');
2125
2126 if (options == ArgumentParsingOptions::Verbatim)
2127 return false;
2128
2129 QString cmdStr;
2130 qsizetype backslashPos = m_position++;
2131 while (m_position < m_input.size() && m_input[m_position].isLetterOrNumber())
2132 cmdStr += m_input[m_position++];
2133
2134 m_endPosition = m_position;
2135 if (!cmdStr.isEmpty()) {
2136 if (s_utilities.macroHash.contains(cmdStr)) {
2137 const Macro &macro = s_utilities.macroHash.value(cmdStr);
2138 if (!macro.m_defaultDef.isEmpty()) {
2139 QString expanded = expandMacroToString(cmdStr, macro);
2140 m_input.replace(backslashPos, m_position - backslashPos, expanded);
2141 m_inputLength = m_input.size();
2142 m_position = backslashPos;
2143 return true;
2144 } else {
2145 location().warning("Macro '%1' does not have a default definition"_L1.arg(cmdStr));
2146 }
2147 } else {
2148 int cmd = s_utilities.cmdHash.value(cmdStr, NOT_A_CMD);
2149 m_position = backslashPos;
2150 if (options != ArgumentParsingOptions::MacroArguments
2151 || cmd == NOT_A_CMD || !cmds[cmd].is_formatting_command) {
2152 location().warning("Unknown macro '%1'"_L1.arg(cmdStr));
2153 ++m_position;
2154 }
2155 }
2156 } else if (m_input[m_position].isSpace()) {
2157 skipAllSpaces();
2158 } else if (m_input[m_position].unicode() == '\\') {
2159 // allow escaping a backslash
2160 m_input.remove(m_position--, 1);
2161 --m_inputLength;
2162 }
2163 return false;
2164}
2165
2166void DocParser::expandMacro(const QString &def, const QStringList &args)
2167{
2168 if (args.isEmpty()) {
2169 appendAtom(Atom(Atom::RawString, def));
2170 } else {
2171 QString rawString;
2172
2173 for (int j = 0; j < def.size(); ++j) {
2174 if (int paramNo = def[j].unicode(); paramNo >= 1 && paramNo <= args.length()) {
2175 if (!rawString.isEmpty()) {
2176 appendAtom(Atom(Atom::RawString, rawString));
2177 rawString.clear();
2178 }
2179 appendAtom(Atom(Atom::String, args[paramNo - 1]));
2180 } else {
2181 rawString += def[j];
2182 }
2183 }
2184 if (!rawString.isEmpty())
2185 appendAtom(Atom(Atom::RawString, rawString));
2186 }
2187}
2188
2189QString DocParser::expandMacroToString(const QString &name, const Macro &macro)
2190{
2191 const QString &def{macro.m_defaultDef};
2192 QString rawString;
2193
2194 if (macro.numParams == 0) {
2195 rawString = macro.m_defaultDef;
2196 } else {
2197 QStringList args{getMacroArguments(name, macro)};
2198
2199 for (int j = 0; j < def.size(); ++j) {
2200 int paramNo = def[j].unicode();
2201 rawString += (paramNo >= 1 && paramNo <= args.length()) ? args[paramNo - 1] : def[j];
2202 }
2203 }
2204 QString matchExpr{macro.m_otherDefs.value("match")};
2205 if (matchExpr.isEmpty())
2206 return rawString;
2207
2208 QString result;
2209 QRegularExpression re(matchExpr);
2210 int capStart = (re.captureCount() > 0) ? 1 : 0;
2211 qsizetype i = 0;
2212 QRegularExpressionMatch match;
2213 while ((match = re.match(rawString, i)).hasMatch()) {
2214 for (int c = capStart; c <= re.captureCount(); ++c)
2215 result += match.captured(c);
2216 i = match.capturedEnd();
2217 }
2218
2219 return result;
2220}
2221
2222Doc::Sections DocParser::getSectioningUnit()
2223{
2224 QString name = getOptionalArgument();
2225
2226 if (name == "section1") {
2227 return Doc::Section1;
2228 } else if (name == "section2") {
2229 return Doc::Section2;
2230 } else if (name == "section3") {
2231 return Doc::Section3;
2232 } else if (name == "section4") {
2233 return Doc::Section4;
2234 } else if (name.isEmpty()) {
2235 return Doc::NoSection;
2236 } else {
2237 location().warning(QStringLiteral("Invalid section '%1'").arg(name));
2238 return Doc::NoSection;
2239 }
2240}
2241
2242/*!
2243 Gets an argument that is enclosed in braces and returns it
2244 without the enclosing braces. On entry, the current character
2245 is the left brace. On exit, the current character is the one
2246 that comes after the right brace.
2247
2248 If \a options is ArgumentParsingOptions::Verbatim, no macro
2249 expansion is performed, nor is the returned string stripped
2250 of extra whitespace.
2251 */
2252QString DocParser::getBracedArgument(ArgumentParsingOptions options)
2253{
2254 QString arg;
2255 int delimDepth = 0;
2256 if (m_position < m_input.size() && m_input[m_position] == '{') {
2257 ++m_position;
2258 while (m_position < m_input.size() && delimDepth >= 0) {
2259 switch (m_input[m_position].unicode()) {
2260 case '{':
2261 ++delimDepth;
2262 arg += QLatin1Char('{');
2263 ++m_position;
2264 break;
2265 case '}':
2266 --delimDepth;
2267 if (delimDepth >= 0)
2268 arg += QLatin1Char('}');
2269 ++m_position;
2270 break;
2271 case '\\':
2272 if (!expandMacro(options))
2273 arg += m_input[m_position++];
2274 break;
2275 default:
2276 if (m_input[m_position].isSpace() && options != ArgumentParsingOptions::Verbatim)
2277 arg += QChar(' ');
2278 else
2279 arg += m_input[m_position];
2280 ++m_position;
2281 }
2282 }
2283 if (delimDepth > 0)
2284 location().warning(QStringLiteral("Missing '}'"));
2285 }
2286 m_endPosition = m_position;
2287 return arg;
2288}
2289
2290/*!
2291 Parses and returns an argument for a command, using
2292 specific parsing \a options.
2293
2294 Typically, an argument ends at the next white-space. However,
2295 braces can be used to group words:
2296
2297 {a few words}
2298
2299 Also, opening and closing parentheses have to match. Thus,
2300
2301 printf("%d\n", x)
2302
2303 is an argument too, although it contains spaces. Finally,
2304 trailing punctuation is not included in an argument, nor is 's.
2305*/
2306QString DocParser::getArgument(ArgumentParsingOptions options)
2307{
2308 skipSpacesOrOneEndl();
2309
2310 int delimDepth = 0;
2311 qsizetype startPos = m_position;
2312 QString arg = getBracedArgument(options);
2313 if (arg.isEmpty()) {
2314 while ((m_position < m_input.size())
2315 && ((delimDepth > 0) || ((delimDepth == 0) && !m_input[m_position].isSpace()))) {
2316 switch (m_input[m_position].unicode()) {
2317 case '(':
2318 case '[':
2319 case '{':
2320 ++delimDepth;
2321 arg += m_input[m_position];
2322 ++m_position;
2323 break;
2324 case ')':
2325 case ']':
2326 case '}':
2327 --delimDepth;
2328 if (m_position == startPos || delimDepth >= 0) {
2329 arg += m_input[m_position];
2330 ++m_position;
2331 }
2332 break;
2333 case '\\':
2334 if (!expandMacro(options))
2335 arg += m_input[m_position++];
2336 break;
2337 default:
2338 arg += m_input[m_position];
2339 ++m_position;
2340 }
2341 }
2342 m_endPosition = m_position;
2343 if ((arg.size() > 1) && (QString(".,:;!?").indexOf(m_input[m_position - 1]) != -1)
2344 && !arg.endsWith("...")) {
2345 arg.truncate(arg.size() - 1);
2346 --m_position;
2347 }
2348 if (arg.size() > 2 && m_input.mid(m_position - 2, 2) == "'s") {
2349 arg.truncate(arg.size() - 2);
2350 m_position -= 2;
2351 }
2352 }
2353 return arg.simplified();
2354}
2355
2356/*!
2357 Gets an argument that is enclosed in brackets and returns it
2358 without the enclosing brackets. On entry, the current character
2359 is the left bracket. On exit, the current character is the one
2360 that comes after the right bracket.
2361 */
2362QString DocParser::getBracketedArgument()
2363{
2364 QString arg;
2365 int delimDepth = 0;
2366 skipSpacesOrOneEndl();
2367 if (m_position < m_input.size() && m_input[m_position] == '[') {
2368 ++m_position;
2369 while (m_position < m_input.size() && delimDepth >= 0) {
2370 switch (m_input[m_position].unicode()) {
2371 case '[':
2372 ++delimDepth;
2373 arg += QLatin1Char('[');
2374 ++m_position;
2375 break;
2376 case ']':
2377 --delimDepth;
2378 if (delimDepth >= 0)
2379 arg += QLatin1Char(']');
2380 ++m_position;
2381 break;
2382 case '\\':
2383 arg += m_input[m_position];
2384 ++m_position;
2385 break;
2386 default:
2387 arg += m_input[m_position];
2388 ++m_position;
2389 }
2390 }
2391 if (delimDepth > 0)
2392 location().warning(QStringLiteral("Missing ']'"));
2393 }
2394 return arg;
2395}
2396
2397
2398/*!
2399 Returns the list of arguments passed to a \a macro with name \a name.
2400
2401 If a macro takes more than a single argument, they are expected to be
2402 wrapped in braces.
2403*/
2404QStringList DocParser::getMacroArguments(const QString &name, const Macro &macro)
2405{
2406 QStringList args;
2407 for (int i = 0; i < macro.numParams; ++i) {
2408 if (macro.numParams == 1 || isLeftBraceAhead()) {
2409 args << getArgument(ArgumentParsingOptions::MacroArguments);
2410 } else {
2411 location().warning(QStringLiteral("Macro '\\%1' invoked with too few"
2412 " arguments (expected %2, got %3)")
2413 .arg(name)
2414 .arg(macro.numParams)
2415 .arg(i));
2416 break;
2417 }
2418 }
2419 return args;
2420}
2421
2422/*!
2423 Returns the next token as an optional argument unless the token is
2424 another command. The next token can be preceded by spaces and an optional
2425 newline.
2426*/
2427QString DocParser::getOptionalArgument()
2428{
2429 skipSpacesOrOneEndl();
2430 if (m_position + 1 < m_input.size() && m_input[m_position] == '\\'
2431 && m_input[m_position + 1].isLetterOrNumber()) {
2432 return QString();
2433 } else {
2434 return getArgument();
2435 }
2436}
2437
2438/*!
2439 \brief Create a string that may optionally span multiple lines as one line.
2440
2441 Process a block of text that may span multiple lines using trailing
2442 backslashes (`\`) as line continuation character. Trailing backslashes and
2443 any newline character that follow them are removed.
2444
2445 Returns a string as if it was one continuous line of text. If trailing
2446 backslashes are removed, the method returns a "simplified" QString, which
2447 means any sequence of internal whitespace is replaced with a single space.
2448
2449 Whitespace at the start and end is always removed from the returned string.
2450
2451 \sa QString::simplified(), QString::trimmed().
2452 */
2453QString DocParser::getRestOfLine()
2454{
2455 auto lineHasTrailingBackslash = [this](bool trailingBackslash) -> bool {
2456 while (m_position < m_inputLength && m_input[m_position] != '\n') {
2457 if (m_input[m_position] == '\\' && !trailingBackslash) {
2458 trailingBackslash = true;
2459 ++m_position;
2460 skipSpacesOnLine();
2461 } else {
2462 trailingBackslash = false;
2463 ++m_position;
2464 }
2465 }
2466 return trailingBackslash;
2467 };
2468
2469 QString rest_of_line;
2470 skipSpacesOnLine();
2471 bool trailing_backslash{ false };
2472 bool return_simplified_string{ false };
2473
2474 for (qsizetype start_position = m_position; m_position < m_inputLength; ++m_position) {
2475 trailing_backslash = lineHasTrailingBackslash(trailing_backslash);
2476
2477 if (!rest_of_line.isEmpty())
2478 rest_of_line += QLatin1Char(' ');
2479 rest_of_line += m_input.sliced(start_position, m_position - start_position);
2480
2481 if (trailing_backslash) {
2482 rest_of_line.truncate(rest_of_line.lastIndexOf('\\'));
2483 return_simplified_string = true;
2484 }
2485
2486 if (m_position < m_inputLength)
2487 ++m_position;
2488
2489 if (!trailing_backslash)
2490 break;
2491 start_position = m_position;
2492 }
2493
2494 if (return_simplified_string)
2495 return rest_of_line.simplified();
2496
2497 return rest_of_line.trimmed();
2498}
2499
2500/*!
2501 The metacommand argument is normally the remaining text to
2502 the right of the metacommand itself. The extra blanks are
2503 stripped and the argument string is returned.
2504 */
2505QString DocParser::getMetaCommandArgument(const QString &cmdStr)
2506{
2507 skipSpacesOnLine();
2508
2509 qsizetype begin = m_position;
2510 int parenDepth = 0;
2511
2512 while (m_position < m_input.size() && (m_input[m_position] != '\n' || parenDepth > 0)) {
2513 if (m_input.at(m_position) == '(')
2514 ++parenDepth;
2515 else if (m_input.at(m_position) == ')')
2516 --parenDepth;
2517 else if (m_input.at(m_position) == '\\' && expandMacro(ArgumentParsingOptions::Default))
2518 continue;
2519 ++m_position;
2520 }
2521 if (m_position == m_input.size() && parenDepth > 0) {
2522 m_position = begin;
2523 location().warning(QStringLiteral("Unbalanced parentheses in '%1'").arg(cmdStr));
2524 }
2525
2526 QString t = m_input.mid(begin, m_position - begin).simplified();
2527 skipSpacesOnLine();
2528 return t;
2529}
2530
2531QString DocParser::getUntilEnd(int cmd)
2532{
2533 int endCmd = endCmdFor(cmd);
2534 QRegularExpression rx("\\\\" + cmdName(endCmd) + "\\b");
2535 QString t;
2536 auto match = rx.match(m_input, m_position);
2537
2538 if (!match.hasMatch()) {
2539 location().warning(QStringLiteral("Missing '\\%1'").arg(cmdName(endCmd)));
2540 m_position = m_input.size();
2541 } else {
2542 qsizetype end = match.capturedStart();
2543 t = m_input.mid(m_position, end - m_position);
2544 m_position = match.capturedEnd();
2545 }
2546 return t;
2547}
2548
2549void DocParser::expandArgumentsInString(QString &str, const QStringList &args)
2550{
2551 if (args.isEmpty())
2552 return;
2553
2554 qsizetype paramNo;
2555 qsizetype j = 0;
2556 while (j < str.size()) {
2557 if (str[j] == '\\' && j < str.size() - 1 && (paramNo = str[j + 1].digitValue()) >= 1
2558 && paramNo <= args.size()) {
2559 const QString &r = args[paramNo - 1];
2560 str.replace(j, 2, r);
2561 j += qMin(1, r.size());
2562 } else {
2563 ++j;
2564 }
2565 }
2566}
2567
2568/*!
2569 Returns the marked-up code following the code-quoting command \a cmd, expanding
2570 any arguments passed in \a argStr.
2571
2572 Uses the \a marker to mark up the code. If it's \c nullptr, resolve the marker
2573 based on the topic and the quoted code itself.
2574*/
2575QString DocParser::getCode(int cmd, CodeMarker *marker, const QString &argStr)
2576{
2577 QString code = untabifyEtc(getUntilEnd(cmd));
2578 expandArgumentsInString(code, argStr.split(" ", Qt::SkipEmptyParts));
2579
2580 int indent = indentLevel(code);
2581 code = dedent(indent, code);
2582
2583 // If we're in a QML topic, check if the QML marker recognizes the code
2584 if (!marker && !m_private->m_topics.isEmpty()
2585 && m_private->m_topics[0].m_topic.startsWith("qml")) {
2586 auto qmlMarker = CodeMarker::markerForLanguage("QML");
2587 marker = (qmlMarker && qmlMarker->recognizeCode(code)) ? qmlMarker : nullptr;
2588 }
2589 if (marker == nullptr)
2590 marker = CodeMarker::markerForCode(code);
2591 return marker->markedUpCode(code, nullptr, location());
2592}
2593
2594bool DocParser::isBlankLine()
2595{
2596 qsizetype i = m_position;
2597
2598 while (i < m_inputLength && m_input[i].isSpace()) {
2599 if (m_input[i] == '\n')
2600 return true;
2601 ++i;
2602 }
2603 return false;
2604}
2605
2606bool DocParser::isLeftBraceAhead()
2607{
2608 int numEndl = 0;
2609 qsizetype i = m_position;
2610
2611 while (i < m_inputLength && m_input[i].isSpace() && numEndl < 2) {
2612 // ### bug with '\\'
2613 if (m_input[i] == '\n')
2614 numEndl++;
2615 ++i;
2616 }
2617 return numEndl < 2 && i < m_inputLength && m_input[i] == '{';
2618}
2619
2620bool DocParser::isLeftBracketAhead()
2621{
2622 int numEndl = 0;
2623 qsizetype i = m_position;
2624
2625 while (i < m_inputLength && m_input[i].isSpace() && numEndl < 2) {
2626 // ### bug with '\\'
2627 if (m_input[i] == '\n')
2628 numEndl++;
2629 ++i;
2630 }
2631 return numEndl < 2 && i < m_inputLength && m_input[i] == '[';
2632}
2633
2634/*!
2635 Skips to the next non-space character or EOL.
2636 */
2637void DocParser::skipSpacesOnLine()
2638{
2639 while ((m_position < m_input.size()) && m_input[m_position].isSpace()
2640 && (m_input[m_position].unicode() != '\n'))
2641 ++m_position;
2642}
2643
2644/*!
2645 Skips spaces and one EOL.
2646 */
2647void DocParser::skipSpacesOrOneEndl()
2648{
2649 qsizetype firstEndl = -1;
2650 while (m_position < m_input.size() && m_input[m_position].isSpace()) {
2651 QChar ch = m_input[m_position];
2652 if (ch == '\n') {
2653 if (firstEndl == -1) {
2654 firstEndl = m_position;
2655 } else {
2656 m_position = firstEndl;
2657 break;
2658 }
2659 }
2660 ++m_position;
2661 }
2662}
2663
2664void DocParser::skipAllSpaces()
2665{
2666 while (m_position < m_inputLength && m_input[m_position].isSpace())
2667 ++m_position;
2668}
2669
2670void DocParser::skipToNextPreprocessorCommand()
2671{
2672 QRegularExpression rx("\\\\‍(?:" + cmdName(CMD_IF) + QLatin1Char('|') + cmdName(CMD_ELSE)
2673 + QLatin1Char('|') + cmdName(CMD_ENDIF) + ")\\b");
2674 auto match = rx.match(m_input, m_position + 1); // ### + 1 necessary?
2675
2676 if (!match.hasMatch())
2677 m_position = m_input.size();
2678 else
2679 m_position = match.capturedStart();
2680}
2681
2683{
2684 switch (cmd) {
2685 case CMD_BADCODE:
2686 return CMD_ENDCODE;
2687 case CMD_CODE:
2688 return CMD_ENDCODE;
2689 case CMD_COMPARESWITH:
2690 return CMD_ENDCOMPARESWITH;
2691 case CMD_DETAILS:
2692 return CMD_ENDDETAILS;
2693 case CMD_DIV:
2694 return CMD_ENDDIV;
2695 case CMD_QML:
2696 return CMD_ENDQML;
2697 case CMD_FOOTNOTE:
2698 return CMD_ENDFOOTNOTE;
2699 case CMD_LEGALESE:
2700 return CMD_ENDLEGALESE;
2701 case CMD_LINK:
2702 return CMD_ENDLINK;
2703 case CMD_LIST:
2704 return CMD_ENDLIST;
2705 case CMD_OMIT:
2706 return CMD_ENDOMIT;
2707 case CMD_QUOTATION:
2708 return CMD_ENDQUOTATION;
2709 case CMD_RAW:
2710 return CMD_ENDRAW;
2711 case CMD_SECTION1:
2712 return CMD_ENDSECTION1;
2713 case CMD_SECTION2:
2714 return CMD_ENDSECTION2;
2715 case CMD_SECTION3:
2716 return CMD_ENDSECTION3;
2717 case CMD_SECTION4:
2718 return CMD_ENDSECTION4;
2719 case CMD_SIDEBAR:
2720 return CMD_ENDSIDEBAR;
2721 case CMD_TABLE:
2722 return CMD_ENDTABLE;
2723 case CMD_TOC:
2724 return CMD_ENDTOC;
2725 default:
2726 return cmd;
2727 }
2728}
2729
2730QString DocParser::cmdName(int cmd)
2731{
2732 return cmds[cmd].name;
2733}
2734
2735QString DocParser::endCmdName(int cmd)
2736{
2737 return cmdName(endCmdFor(cmd));
2738}
2739
2740QString DocParser::untabifyEtc(const QString &str)
2741{
2742 QString result;
2743 result.reserve(str.size());
2744 int column = 0;
2745
2746 for (const auto &character : str) {
2747 if (character == QLatin1Char('\r'))
2748 continue;
2749 if (character == QLatin1Char('\t')) {
2750 result += &" "[column % s_tabSize];
2751 column = ((column / s_tabSize) + 1) * s_tabSize;
2752 continue;
2753 }
2754 if (character == QLatin1Char('\n')) {
2755 while (result.endsWith(QLatin1Char(' ')))
2756 result.chop(1);
2757 result += character;
2758 column = 0;
2759 continue;
2760 }
2761 result += character;
2762 ++column;
2763 }
2764
2765 while (result.endsWith("\n\n"))
2766 result.truncate(result.size() - 1);
2767 while (result.startsWith(QLatin1Char('\n')))
2768 result = result.mid(1);
2769
2770 return result;
2771}
2772
2773int DocParser::indentLevel(const QString &str)
2774{
2775 int minIndent = INT_MAX;
2776 int column = 0;
2777
2778 for (const auto &character : str) {
2779 if (character == '\n') {
2780 column = 0;
2781 } else {
2782 if (character != ' ' && column < minIndent)
2783 minIndent = column;
2784 ++column;
2785 }
2786 }
2787 return minIndent;
2788}
2789
2790QString DocParser::dedent(int level, const QString &str)
2791{
2792 if (level == 0)
2793 return str;
2794
2795 QString result;
2796 int column = 0;
2797
2798 for (const auto &character : str) {
2799 if (character == QLatin1Char('\n')) {
2800 result += '\n';
2801 column = 0;
2802 } else {
2803 if (column >= level)
2804 result += character;
2805 ++column;
2806 }
2807 }
2808 return result;
2809}
2810
2811/*!
2812 Returns \c true if \a atom represents a code snippet.
2813 */
2814bool DocParser::isCode(const Atom *atom)
2815{
2816 Atom::AtomType type = atom->type();
2817 return (type == Atom::Code || type == Atom::Qml);
2818}
2819
2820/*!
2821 Returns \c true if \a atom represents quoting information.
2822 */
2823bool DocParser::isQuote(const Atom *atom)
2824{
2825 Atom::AtomType type = atom->type();
2826 return (type == Atom::CodeQuoteArgument || type == Atom::CodeQuoteCommand
2827 || type == Atom::SnippetCommand || type == Atom::SnippetIdentifier
2828 || type == Atom::SnippetLocation);
2829}
2830
2831/*!
2832 \internal
2833 Processes the arguments passed to the \\compareswith block command.
2834 The arguments are stored as text within the first atom of the block
2835 (Atom::ComparesLeft).
2836
2837 Extracts the comparison category and the list of types, and stores
2838 the information into a map accessed via \a priv.
2839*/
2840static void processComparesWithCommand(DocPrivate *priv, const Location &location)
2841{
2842 static auto take_while = [](QStringView input, auto predicate) {
2843 QStringView::size_type end{0};
2844
2845 while (end < input.size() && std::invoke(predicate, input[end]))
2846 ++end;
2847
2848 return std::make_tuple(input.sliced(0, end), input.sliced(end));
2849 };
2850
2851 static auto peek = [](QStringView input, QChar c) {
2852 return !input.empty() && input.first() == c;
2853 };
2854
2855 static auto skip_one = [](QStringView input) {
2856 if (input.empty()) return std::make_tuple(QStringView{}, input);
2857 else return std::make_tuple(input.sliced(0, 1), input.sliced(1));
2858 };
2859
2860 static auto enclosed = [](QStringView input, QChar open, QChar close) {
2861 if (!peek(input, open)) return std::make_tuple(QStringView{}, input);
2862
2863 auto [opened, without_open] = skip_one(input);
2864 auto [parsed, remaining] = take_while(without_open, [close](QChar c){ return c != close; });
2865
2866 if (remaining.empty()) return std::make_tuple(QStringView{}, input);
2867
2868 auto [closed, without_close] = skip_one(remaining);
2869
2870 return std::make_tuple(parsed.trimmed(), without_close);
2871 };
2872
2873 static auto one_of = [](auto first, auto second) {
2874 return [first, second](QStringView input) {
2875 auto [parsed, remaining] = std::invoke(first, input);
2876
2877 if (parsed.empty()) return std::invoke(second, input);
2878 else return std::make_tuple(parsed, remaining);
2879 };
2880 };
2881
2882 static auto collect = [](QStringView input, auto parser) {
2883 QStringList collected{};
2884
2885 while (true) {
2886 auto [parsed, remaining] = std::invoke(parser, input);
2887
2888 if (parsed.empty()) break;
2889 collected.append(parsed.toString());
2890
2891 input = remaining;
2892 };
2893
2894 return collected;
2895 };
2896
2897 static auto spaces = [](QStringView input) {
2898 return take_while(input, [](QChar c){ return c.isSpace(); });
2899 };
2900
2901 static auto word = [](QStringView input) {
2902 return take_while(input, [](QChar c){ return !c.isSpace(); });
2903 };
2904
2905 static auto parse_argument = [](QStringView input) {
2906 auto [_, without_spaces] = spaces(input);
2907
2908 return one_of(
2909 [](QStringView input){ return enclosed(input, '{', '}'); },
2910 word
2911 )(without_spaces);
2912 };
2913
2914 const QString cmd{DocParser::cmdName(CMD_COMPARESWITH)};
2916
2917 auto *atom = text.firstAtom();
2918 QStringList segments = collect(atom->string(), parse_argument);
2919
2920 QString categoryString;
2921 if (!segments.isEmpty())
2922 categoryString = segments.takeFirst();
2923 auto category = comparisonCategoryFromString(categoryString.toStdString());
2924
2925 if (category == ComparisonCategory::None) {
2926 location.warning(u"Invalid argument to \\%1 command: `%2`"_s.arg(cmd, categoryString),
2927 u"Valid arguments are `strong`, `weak`, `partial`, or `equality`."_s);
2928 return;
2929 }
2930
2931 if (segments.isEmpty()) {
2932 location.warning(u"Missing argument to \\%1 command."_s.arg(cmd),
2933 u"Provide at least one type name, or a list of types separated by spaces."_s);
2934 return;
2935 }
2936
2937 // Store cleaned-up type names back into the atom
2938 segments.removeDuplicates();
2939 atom->setString(segments.join(QLatin1Char(';')));
2940
2941 // Add an entry to meta-command map for error handling in CppCodeParser
2942 priv->m_metaCommandMap[cmd].append(ArgPair(categoryString, atom->string()));
2943 priv->m_metacommandsUsed.insert(cmd);
2944
2946 const auto end{priv->extra->m_comparesWithMap.cend()};
2947 priv->extra->m_comparesWithMap.insert(end, category, text);
2948}
2949
2950QT_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:44
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:420
#define CONFIG_IGNOREWORDS
Definition config.h:380
#define CONFIG_QUOTINGINFORMATION
Definition config.h:409
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)
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