13#include <QtCore/qfile.h>
14#include <QtCore/qregularexpression.h>
15#include <QtCore/qtextstream.h>
23using namespace Qt::StringLiterals;
139} cmds[] = { {
"a",
CMD_A,
true },
141 {
"b",
CMD_B,
true },
146 {
"c",
CMD_C,
true },
154 {
"e",
CMD_E,
true },
179 {
"i",
CMD_I,
true },
252
253
254
255
258 qsizetype colonPos = link.indexOf(
':');
259 QString cleaned{link};
260 cleaned.replace(
"\\#"_L1,
"#"_L1);
261 if ((colonPos == -1) || (!link.startsWith(
"file:") && !link.startsWith(
"mailto:")))
263 return cleaned.mid(colonPos + 1).simplified();
273 while (cmds[i]
.name) {
277 Location::internalError(QStringLiteral(
"command %1 missing").arg(i));
283 const auto &outputFormats = config.getOutputFormats();
284 for (
const auto &format : outputFormats)
285 DocParser::s_quoting = DocParser::s_quoting
290 DocParser::file_resolver = &file_resolver;
294
295
296
297
298
299
300
301
303 const QSet<QString> &metaCommandSet,
const QSet<QString> &possibleTopics)
307 m_inputLength = m_input.size();
308 m_cachedLocation = docPrivate->m_start_loc;
309 m_cachedPosition = 0;
310 m_private = docPrivate;
314 m_paragraphState = OutsideParagraph;
315 m_inTableHeader =
false;
316 m_inTableRow =
false;
317 m_inTableItem =
false;
318 m_indexStartedParagraph =
false;
324 m_openedCommands.push(CMD_OMIT);
328 Atom *currentLinkAtom =
nullptr;
330 QStack<
bool> preprocessorSkipping;
331 int numPreprocessorSkipping = 0;
337 QString codeLanguage;
339 while (m_position < m_inputLength) {
340 QChar ch = m_input.at(m_position);
342 switch (ch.unicode()) {
345 m_backslashPosition = m_position;
347 while (m_position < m_inputLength) {
348 ch = m_input.at(m_position);
349 if (ch.isLetterOrNumber()) {
356 m_endPosition = m_position;
357 if (cmdStr.isEmpty()) {
358 if (m_position < m_inputLength) {
359 QChar nextCh = m_input.at(m_position);
360 if (nextCh ==
'\\') {
361 appendEscapedIdentifier();
362 }
else if (nextCh.isSpace()) {
365 appendChar(QLatin1Char(
' '));
368 appendChar(m_input.at(m_position++));
385 m_private->m_params.insert(p1);
389 appendAtom(Atom(Atom::CodeBad,
390 getCode(CMD_BADCODE, marker, getMetaCommandArgument(cmdStr))));
397 location().warning(QStringLiteral(
"'\\bold' is deprecated. Use '\\b'"));
404 enterPara(Atom::BriefLeft, Atom::BriefRight);
408 p1 = untabifyEtc(getArgument(ArgumentParsingOptions::Verbatim));
409 marker = CodeMarker::markerForCode(p1);
410 appendAtom(
Atom(
Atom::C, marker->markedUpCode(p1,
nullptr, location())));
414 enterPara(Atom::CaptionLeft, Atom::CaptionRight);
418 codeLanguage = getLanguageArgument(&marker);
422 appendAtom(Atom(Atom::Code, getCode(CMD_CODE, marker, getMetaCommandArgument(cmdStr)), codeLanguage));
426 appendAtom(Atom(Atom::Qml,
427 getCode(CMD_QML, CodeMarker::markerForLanguage(QLatin1String(
"QML")),
428 getMetaCommandArgument(cmdStr))));
433 if (!isBlankLine()) {
434 if (isLeftBraceAhead()) {
435 enterPara(Atom::DetailsSummaryLeft, Atom::DetailsSummaryRight);
437 location().warning(u"Expected '{' when parsing \\%1 argument"_s.arg(cmdName(cmd)));
438 std::ignore = getRestOfLine();
441 m_openedCommands.push(cmd);
450 p1 = getArgument(ArgumentParsingOptions::Verbatim);
452 m_openedCommands.push(cmd);
464 if (isCode(m_lastAtom) && m_lastAtom->string().endsWith(
"\n\n"))
469 QString arg = getOptionalArgument();
476 if (isCode(m_lastAtom) && m_lastAtom->string().endsWith(
"\n\n"))
479 int indent = arg.toInt();
480 for (
int i = 0; i < indent; ++i)
482 appendToCode(
"...\n");
486 if (!preprocessorSkipping.empty()) {
487 if (preprocessorSkipping.top()) {
488 --numPreprocessorSkipping;
490 ++numPreprocessorSkipping;
492 preprocessorSkipping.top() = !preprocessorSkipping.top();
493 (
void)getRestOfLine();
494 if (numPreprocessorSkipping)
495 skipToNextPreprocessorCommand();
498 QStringLiteral(
"Unexpected '\\%1'").arg(cmdName(
CMD_ELSE)));
508 if (closeCommand(cmd)) {
514 if (preprocessorSkipping.size() > 0) {
515 if (preprocessorSkipping.pop())
516 --numPreprocessorSkipping;
517 (
void)getRestOfLine();
518 if (numPreprocessorSkipping)
519 skipToNextPreprocessorCommand();
522 QStringLiteral(
"Unexpected '\\%1'").arg(cmdName(
CMD_ENDIF)));
526 if (closeCommand(cmd)) {
532 if (closeCommand(cmd)) {
540 if (closeCommand(cmd)) {
542 if (m_openedLists.top().isStarted()) {
543 appendAtom(Atom(Atom::ListItemRight, m_openedLists.top().styleString()));
544 appendAtom(Atom(Atom::ListRight, m_openedLists.top().styleString()));
553 if (closeCommand(cmd)) {
560 QStringLiteral(
"Unexpected '\\%1'").arg(cmdName(
CMD_ENDRAW)));
575 if (closeCommand(cmd)) {
581 if (closeCommand(cmd)) {
587 if (openCommand(cmd)) {
594 if (isLeftBracketAhead())
595 p2 = getBracketedArgument();
598 appendAtom(Atom(Atom::AnnotatedList, getArgument(), p2));
602 appendAtom(Atom(Atom::SinceList, getRestOfLine().simplified()));
606 if (isLeftBracketAhead())
607 p2 = getBracketedArgument();
610 QString arg1 = getArgument();
611 QString arg2 = getOptionalArgument();
614 appendAtom(Atom(Atom::GeneratedList, arg1, p2));
617 if (m_openedCommands.top() == CMD_TABLE) {
620 m_inTableHeader =
true;
622 if (m_openedCommands.contains(CMD_TABLE))
623 location().warning(QStringLiteral(
"Cannot use '\\%1' within '\\%2'")
625 cmdName(m_openedCommands.top())));
628 QStringLiteral(
"Cannot use '\\%1' outside of '\\%2'")
633 location().warning(QStringLiteral(
634 "'\\i' is deprecated. Use '\\e' for italic or '\\li' for list item"));
644 preprocessorSkipping.push(!Tokenizer::isTrue(getRestOfLine()));
645 if (preprocessorSkipping.top())
646 ++numPreprocessorSkipping;
647 if (numPreprocessorSkipping)
648 skipToNextPreprocessorCommand();
655 enterPara(Atom::ImportantLeft, Atom::ImportantRight);
659 QString fileName = getArgument();
660 QStringList parameters;
662 if (isLeftBraceAhead()) {
663 identifier = getArgument();
664 while (isLeftBraceAhead() && parameters.size() < 9)
665 parameters << getArgument();
667 identifier = getRestOfLine();
669 include(fileName, identifier, parameters);
676 if (m_paragraphState == OutsideParagraph) {
678 m_indexStartedParagraph =
true;
681 if (m_indexStartedParagraph
684 m_indexStartedParagraph =
false;
690 insertKeyword(getRestOfLine());
693 if (m_openedCommands.top() != CMD_TOC) {
694 location().warning(
"Command '\\%1' outside of '\\%2'"_L1
695 .arg(cmdName(cmd), cmdName(CMD_TOC)));
701 if (isLeftBracketAhead())
702 p2 = getBracketedArgument();
706 appendAtom(LinkAtom(p1, p2, location()));
708 if (isLeftBraceAhead()) {
713 appendAtom(Atom(Atom::String, cleanLink(p1)));
722 if (openCommand(cmd))
727 if (openCommand(cmd)) {
730 appendAtom(LinkAtom(p1, {}, location()));
732 skipSpacesOrOneEndl();
736 if (openCommand(cmd)) {
738 m_openedLists.push(OpenedList(location(), getOptionalArgument()));
748 enterPara(Atom::NoteLeft, Atom::NoteRight);
750 case CMD_NOTRANSLATE:
754 location().warning(QStringLiteral(
"'\\o' is deprecated. Use '\\li'"));
758 if (m_openedCommands.top() == CMD_LIST) {
759 if (m_openedLists.top().isStarted())
760 appendAtom(Atom(Atom::ListItemRight, m_openedLists.top().styleString()));
762 appendAtom(Atom(Atom::ListLeft, m_openedLists.top().styleString()));
763 m_openedLists.top().next();
764 appendAtom(Atom(Atom::ListItemNumber, m_openedLists.top().numberString()));
765 appendAtom(Atom(Atom::ListItemLeft, m_openedLists.top().styleString()));
767 }
else if (m_openedCommands.top() == CMD_TABLE) {
770 if (isLeftBraceAhead()) {
772 if (isLeftBraceAhead())
776 if (!m_inTableHeader && !m_inTableRow) {
778 QStringLiteral(
"Missing '\\%1' or '\\%2' before '\\%3'")
783 }
else if (m_inTableItem) {
785 m_inTableItem =
false;
788 appendAtom(Atom(Atom::TableItemLeft, p1, p2));
789 m_inTableItem =
true;
792 QStringLiteral(
"Command '\\%1' outside of '\\%2' and '\\%3'")
801 if (!m_private->m_enumItemList.contains(p1))
802 m_private->m_enumItemList.append(p1);
803 if (!m_private->m_omitEnumItemList.contains(p1))
804 m_private->m_omitEnumItemList.append(p1);
805 skipSpacesOrOneEndl();
807 while (m_position < m_inputLength && !isBlankLine()) {
809 if (qsizetype pos = m_position; pos < m_input.size()
810 && m_input.at(pos++).unicode() ==
'\\') {
812 while (pos < m_input.size() && m_input[pos].isLetterOrNumber())
813 nextCmdStr += m_input[pos++];
815 if (nextCmd == cmd || nextCmd ==
CMD_VALUE)
824 p1 = getRestOfLine();
825 if (openCommand(cmd))
829 if (closeCommand(cmd)) {
841 QString rest = getRestOfLine();
846 if (cmd == CMD_PRINTLINE)
847 appendToCode(m_quoter.quoteLine(location(), cmdStr, rest), atomType, codeLanguage);
848 else if (cmd == CMD_PRINTTO)
849 appendToCode(m_quoter.quoteTo(location(), cmdStr, rest), atomType, codeLanguage);
851 appendToCode(m_quoter.quoteUntil(location(), cmdStr, rest), atomType, codeLanguage);
855 if (openCommand(cmd)) {
863 codeLanguage = getLanguageArgument(&marker);
866 QString fileName = getArgument();
872 marker = CodeMarker::markerForFileName(fileName);
874 quoteFromFile(fileName, marker);
876 appendAtom(Atom(Atom::Code, m_quoter.quoteTo(location(), cmdStr, QString()), codeLanguage));
883 p1 = getRestOfLine();
885 location().warning(QStringLiteral(
"Missing format name after '\\%1'")
893 if (m_openedCommands.top() == CMD_TABLE) {
895 if (isLeftBraceAhead())
896 p1 = getArgument(ArgumentParsingOptions::Verbatim);
901 if (m_openedCommands.contains(CMD_TABLE))
902 location().warning(QStringLiteral(
"Cannot use '\\%1' within '\\%2'")
904 cmdName(m_openedCommands.top())));
906 location().warning(QStringLiteral(
"Cannot use '\\%1' outside of '\\%2'")
926 if (openCommand(cmd)) {
933 QString rest = getRestOfLine();
938 m_quoter.quoteLine(location(), cmdStr, rest);
943 QString rest = getRestOfLine();
948 m_quoter.quoteTo(location(), cmdStr, rest);
953 QString rest = getRestOfLine();
958 m_quoter.quoteUntil(location(), cmdStr, rest);
963 startFormat(p1, cmd);
967 codeLanguage = getLanguageArgument(&marker);
970 QString snippet = getArgument();
971 QString identifier = getRestOfLine();
978 marker = CodeMarker::markerForFileName(snippet);
980 quoteFromFile(snippet, marker);
981 appendToCode(m_quoter.quoteSnippet(location(), identifier), marker->atomType(), codeLanguage);
992 p1 = getOptionalArgument();
993 p2 = getOptionalArgument();
994 if (openCommand(cmd)) {
996 appendAtom(Atom(Atom::TableLeft, p1, p2));
997 m_inTableHeader =
false;
998 m_inTableRow =
false;
999 m_inTableItem =
false;
1003 location().report(
"\\%1 is deprecated and will be removed in a future version."_L1.arg(cmdName(cmd)));
1004 if (isLeftBraceAhead())
1008 if (m_openedCommands.top() == CMD_TABLE && !m_inTableItem) {
1009 location().warning(
"Found a \\target command outside table item in a table.\n"
1010 "Move the \\target inside the \\li to resolve this warning.");
1012 insertTarget(getRestOfLine());
1016 enterPara(Atom::TitleLeft, Atom::TitleRight);
1020 if (m_paragraphState != InSingleLineParagraph)
1024 if (openCommand(cmd)) {
1030 if (closeCommand(cmd)) {
1048 uint unicodeChar = p1.toUInt(&ok, 0);
1049 if (!ok || (unicodeChar == 0x0000) || (unicodeChar > 0xFFFE))
1051 QStringLiteral(
"Invalid Unicode character '%1' specified with '%2'")
1059 if (m_openedLists.top().style() == OpenedList::Value) {
1062 if (p1.startsWith(QLatin1String(
"[since "))
1063 && p1.endsWith(QLatin1String(
"]"))) {
1064 p2 = p1.mid(7, p1.size() - 8);
1067 if (!m_private->m_enumItemList.contains(p1))
1068 m_private->m_enumItemList.append(p1);
1070 m_openedLists.top().next();
1074 if (!p2.isEmpty()) {
1081 skipSpacesOrOneEndl();
1090 enterPara(Atom::WarningLeft, Atom::WarningRight);
1096 if (metaCommandSet.contains(cmdStr)) {
1098 QString bracketedArg;
1099 m_private->m_metacommandsUsed.insert(cmdStr);
1100 if (isLeftBracketAhead())
1101 bracketedArg = getBracketedArgument();
1104 if (m_position < m_inputLength
1105 && (cmdStr == QLatin1String(
"obsolete")
1106 || cmdStr == QLatin1String(
"deprecated")))
1107 m_input[m_position] =
'\n';
1109 arg = getMetaCommandArgument(cmdStr);
1111 if (possibleTopics.contains(cmdStr)) {
1112 if (!cmdStr.endsWith(QLatin1String(
"propertygroup")))
1115 }
else if (s_utilities
.macroHash.contains(cmdStr)) {
1117 QStringList macroArgs;
1118 int numPendingFi = 0;
1119 int numFormatDefs = 0;
1120 for (
auto it = macro.m_otherDefs.constBegin();
1121 it != macro.m_otherDefs.constEnd(); ++it) {
1122 if (it.key() !=
"match") {
1123 if (numFormatDefs == 0)
1124 macroArgs = getMacroArguments(cmdStr, macro);
1126 expandMacro(*it, macroArgs);
1128 if (it == macro.m_otherDefs.constEnd()) {
1136 while (numPendingFi-- > 0)
1139 if (!macro.m_defaultDef.isEmpty()) {
1140 if (numFormatDefs > 0) {
1141 macro.m_defaultDefLocation.warning(
1142 QStringLiteral(
"Macro cannot have both "
1143 "format-specific and qdoc-"
1144 "syntax definitions"));
1146 QString expanded = expandMacroToString(cmdStr, macro);
1147 m_input.replace(m_backslashPosition,
1148 m_endPosition - m_backslashPosition, expanded);
1149 m_inputLength = m_input.size();
1150 m_position = m_backslashPosition;
1153 }
else if (isAutoLinkString(cmdStr)) {
1156 if (!cmdStr.endsWith(
"propertygroup")) {
1159 location().warning(QStringLiteral(
"Unknown command '\\%1'").arg(cmdStr),
1160 detailsUnknownCommand(metaCommandSet, cmdStr));
1171 qsizetype dashCount = 1;
1175 while ((m_position < m_inputLength) && (m_input.at(m_position) ==
'-')) {
1180 if (dashCount == 3) {
1182 const QChar emDash(8212);
1184 }
else if (dashCount == 2) {
1186 const QChar enDash(8211);
1192 for (qsizetype i = 0; i < dashCount; ++i)
1201 if (m_paragraphState != InBraceDelimitedParagraph)
1210 auto format = m_pendingFormats.find(m_braceDepth);
1211 if (format == m_pendingFormats.end()) {
1212 if (m_paragraphState == InBraceDelimitedParagraph) {
1222 if (m_indexStartedParagraph)
1227 if (currentLinkAtom && currentLinkAtom->string().endsWith(
"::")) {
1231 currentLinkAtom->concatenateString(suffix);
1233 currentLinkAtom =
nullptr;
1237 m_pendingFormats.erase(format);
1243 if (m_position + 2 < m_inputLength)
1244 if (m_input.at(m_position + 1) ==
'/')
1245 if (m_input.at(m_position + 2) ==
'!') {
1248 if (m_input.at(m_position - 1) ==
'\n')
1264 if (m_paragraphState == OutsideParagraph) {
1276 && (m_paragraphState == InSingleLineParagraph || isBlankLine())) {
1289 qsizetype startPos = m_position;
1291 bool autolink = (!m_pendingFormats.isEmpty() &&
1293 || m_paragraphState == InSingleLineParagraph ?
1294 false : isAutoLinkString(m_input, m_position);
1295 if (m_position == startPos) {
1296 if (!ch.isSpace()) {
1301 QString word = m_input.mid(startPos, m_position - startPos);
1303 if (s_ignoreWords.contains(word) || word.startsWith(QString(
"__")))
1318 if (m_openedCommands.top() == CMD_LEGALESE) {
1320 m_openedCommands.pop();
1323 if (m_openedCommands.top() != CMD_OMIT) {
1325 QStringLiteral(
"Missing '\\%1'").arg(endCmdName(m_openedCommands.top())));
1326 }
else if (preprocessorSkipping.size() > 0) {
1327 location().warning(QStringLiteral(
"Missing '\\%1'").arg(cmdName(
CMD_ENDIF)));
1331 appendAtom(Atom(Atom::SectionRight, QString::number(m_currentSection)));
1339
1340
1343 while (!m_openedInputs.isEmpty() && m_openedInputs.top() <= m_position) {
1344 m_cachedLocation.pop();
1345 m_cachedPosition = m_openedInputs.pop();
1347 while (m_cachedPosition < m_position)
1348 m_cachedLocation.advance(m_input.at(m_cachedPosition++));
1349 return m_cachedLocation;
1352QString
DocParser::detailsUnknownCommand(
const QSet<QString> &metaCommandSet,
const QString &str)
1354 QSet<QString> commandSet = metaCommandSet;
1356 while (cmds[i]
.name !=
nullptr) {
1357 commandSet.insert(cmds[i]
.name);
1361 return "Maybe you meant '\\%1'?"_L1.arg(suggestName(str, commandSet));
1365
1366
1367
1368
1369
1370
1371
1373 const QString &cmdString,
const QString &previousDefinition)
1375 if (duplicateDefinition.isEmpty()) {
1376 location.warning(
"Expected an argument for \\%1"_L1.arg(cmdString));
1379 "Duplicate %3 name '%1'. The previous occurrence is here: %2"_L1
1380 .arg(duplicateDefinition, previousDefinition, cmdString));
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398void DocParser::insertTarget(
const QString &target)
1400 if (target.isEmpty() || m_targetMap.contains(target))
1401 return warnAboutEmptyOrPreexistingTarget(location(), target,
1402 s_utilities.cmdHash.key(CMD_TARGET), m_targetMap[target].toString());
1404 m_targetMap.insert(target, location());
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425void DocParser::insertKeyword(
const QString &keyword)
1427 if (keyword.isEmpty() || m_targetMap.contains(keyword))
1428 return warnAboutEmptyOrPreexistingTarget(location(), keyword,
1429 s_utilities.cmdHash.key(CMD_KEYWORD), m_targetMap[keyword].toString());
1431 m_targetMap.insert(keyword, location());
1438void DocParser::include(
const QString &fileName,
const QString &identifier,
const QStringList ¶meters)
1441 location().fatal(QStringLiteral(
"Too many nested '\\%1's").arg(cmdName(
CMD_INCLUDE)));
1442 QString filePath = Config::instance().getIncludeFilePath(fileName);
1443 if (filePath.isEmpty()) {
1444 location().warning(QStringLiteral(
"Cannot find qdoc include file '%1'").arg(fileName));
1446 QFile inFile(filePath);
1447 if (!inFile.open(QFile::ReadOnly)) {
1449 QStringLiteral(
"Cannot open qdoc include file '%1'").arg(filePath));
1451 location().push(fileName);
1452 QTextStream inStream(&inFile);
1453 QString includedContent = inStream.readAll();
1456 if (identifier.isEmpty()) {
1457 expandArgumentsInString(includedContent, parameters);
1458 m_input.insert(m_position, includedContent);
1459 m_inputLength = m_input.size();
1460 m_openedInputs.push(m_position + includedContent.size());
1462 auto isSnippetMarker = [&identifier](QStringView trimmedLine) ->
bool {
1463 if (!trimmedLine.startsWith(QLatin1String(
"//!")))
1465 auto bracketStart = trimmedLine.indexOf(QLatin1Char(
'['));
1466 auto bracketEnd = trimmedLine.indexOf(QLatin1Char(
']'));
1467 if (bracketStart < 0 || bracketEnd <= bracketStart)
1469 auto name = trimmedLine.mid(bracketStart + 1, bracketEnd - bracketStart - 1)
1471 return name == identifier;
1474 QStringList lineBuffer = includedContent.split(QLatin1Char(
'\n'));
1475 qsizetype bufLen{lineBuffer.size()};
1477 QStringView trimmedLine;
1478 for (i = 0; i < bufLen; ++i) {
1479 trimmedLine = QStringView{lineBuffer[i]}.trimmed();
1480 if (isSnippetMarker(trimmedLine))
1483 if (i < bufLen - 1) {
1487 QStringLiteral(
"Cannot find '%1' in '%2'").arg(identifier, filePath));
1492 trimmedLine = QStringView{lineBuffer[i]}.trimmed();
1493 if (isSnippetMarker(trimmedLine))
1496 result += lineBuffer[i] + QLatin1Char(
'\n');
1498 }
while (i < bufLen);
1500 expandArgumentsInString(result, parameters);
1501 if (result.isEmpty()) {
1502 location().warning(QStringLiteral(
"Empty qdoc snippet '%1' in '%2'")
1503 .arg(identifier, filePath));
1505 m_input.insert(m_position, result);
1506 m_inputLength = m_input.size();
1507 m_openedInputs.push(m_position + result.size());
1514void DocParser::startFormat(
const QString &format,
int cmd)
1518 for (
const auto &item : std::as_const(m_pendingFormats)) {
1519 if (item == format) {
1520 location().warning(QStringLiteral(
"Cannot nest '\\%1' commands").arg(cmdName(cmd)));
1527 if (isLeftBraceAhead()) {
1528 skipSpacesOrOneEndl();
1529 m_pendingFormats.insert(m_braceDepth, format);
1533 const auto &arg{getArgument()};
1538 m_indexStartedParagraph =
false;
1547 int outer = m_openedCommands.top();
1550 if ((cmd == CMD_COMPARESWITH || cmd == CMD_TOC)
1551 && m_openedCommands.contains(cmd)) {
1552 location().warning(u"Cannot nest '\\%1' commands"_s.arg(cmdName(cmd)));
1569 m_openedCommands.push(cmd);
1572 QStringLiteral(
"Can't use '\\%1' in '\\%2'").arg(cmdName(cmd), cmdName(outer)));
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654inline bool DocParser::isAutoLinkString(
const QString &word)
1656 qsizetype start = 0;
1657 return isAutoLinkString(word, start) && (start == word.size());
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681bool DocParser::isAutoLinkString(
const QString &word, qsizetype &curPos)
1683 qsizetype len = word.size();
1684 qsizetype startPos = curPos;
1685 int numUppercase = 0;
1686 int numLowercase = 0;
1687 int numStrangeSymbols = 0;
1689 while (curPos < len) {
1690 unsigned char latin1Ch = word.at(curPos).toLatin1();
1691 if (islower(latin1Ch)) {
1694 }
else if (isupper(latin1Ch)) {
1695 if (curPos > startPos)
1698 }
else if (isdigit(latin1Ch)) {
1699 if (curPos > startPos)
1703 }
else if (latin1Ch ==
'_' || latin1Ch ==
'@') {
1704 ++numStrangeSymbols;
1706 }
else if ((latin1Ch ==
':') && (curPos < len - 1)
1707 && (word.at(curPos + 1) == QLatin1Char(
':'))) {
1708 ++numStrangeSymbols;
1710 }
else if (latin1Ch ==
'(') {
1711 if ((curPos < len - 1) && (word.at(curPos + 1) == QLatin1Char(
')'))) {
1712 ++numStrangeSymbols;
1722 return ((numUppercase >= 1 && numLowercase >= 2) || (numStrangeSymbols > 0 && (numUppercase + numLowercase >= 1)));
1727 if (endCmdFor(m_openedCommands.top()) == endCmd && m_openedCommands.size() > 1) {
1728 m_openedCommands.pop();
1731 bool contains =
false;
1732 QStack<
int> opened2 = m_openedCommands;
1733 while (opened2.size() > 1) {
1742 while (endCmdFor(m_openedCommands.top()) != endCmd && m_openedCommands.size() > 1) {
1744 QStringLiteral(
"Missing '\\%1' before '\\%2'")
1745 .arg(endCmdName(m_openedCommands.top()), cmdName(endCmd)));
1746 m_openedCommands.pop();
1749 location().warning(QStringLiteral(
"Unexpected '\\%1'").arg(cmdName(endCmd)));
1760 m_currentSection =
static_cast<
Doc::
Sections>(unit);
1763 endSection(unit, cmd);
1766 appendAtom(Atom(Atom::SectionLeft, QString::number(unit)));
1769 m_private
->extra->m_tableOfContentsLevels.append(unit);
1770 enterPara(Atom::SectionHeadingLeft, Atom::SectionHeadingRight, QString::number(unit));
1771 m_currentSection = unit;
1777 appendAtom(Atom(Atom::SectionRight, QString::number(m_currentSection)));
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1811 const QString imageFileName = getArgument();
1813 bool hasAltTextArgument{
false};
1814 if (isLeftBraceAhead()) {
1815 hasAltTextArgument =
true;
1816 imageText = getArgument();
1818 imageText = getRestOfLine();
1821 if (imageText.length() > 1) {
1822 if (imageText.front() ==
'"' && imageText.back() ==
'"') {
1823 imageText.removeFirst();
1824 imageText.removeLast();
1828 if (!hasAltTextArgument && imageText.isEmpty() && Config::instance().reportMissingAltTextForImages())
1829 location().report(QStringLiteral(
"\\%1 %2 is without a textual description, "
1830 "QDoc will not generate an alt text for the image.")
1832 .arg(imageFileName));
1833 appendAtom(
Atom(imageAtom, imageFileName));
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1852 const QString cmd{
"overload"};
1855 m_private->m_metacommandsUsed.insert(cmd);
1856 QString overloadArgument = isBlankLine() ? getMetaCommandArgument(cmd) : getRestOfLine();
1861 if (overloadArgument.trimmed() ==
"primary")
1862 overloadArgument =
"__qdoc_primary_overload__"_L1;
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1882 auto line_comment = [
this]() ->
bool {
1884 if (m_position + 2 > m_inputLength)
1886 if (m_input[m_position].unicode() ==
'/') {
1887 if (m_input[m_position + 1].unicode() ==
'/') {
1888 if (m_input[m_position + 2].unicode() ==
'!') {
1896 auto skip_everything_until_newline = [
this]() ->
void {
1897 while (m_position < m_inputLength && m_input[m_position] !=
'\n')
1903 while (m_position < m_inputLength && m_input[m_position] !=
'\n') {
1906 bool skipMe =
false;
1908 if (m_input[m_position] ==
'{') {
1909 target = getArgument();
1910 if (isLeftBraceAhead()) {
1911 str = getArgument();
1915 if (target.endsWith(
"::"))
1921 target = getArgument();
1922 str = cleanLink(target);
1923 if (target == QLatin1String(
"and") || target == QLatin1String(
"."))
1929 also << LinkAtom(target, {}, location())
1938 skip_everything_until_newline();
1940 if (m_position < m_inputLength && m_input[m_position] ==
',') {
1943 skip_everything_until_newline();
1944 skipSpacesOrOneEndl();
1945 }
else if (m_position >= m_inputLength || m_input[m_position] !=
'\n') {
1946 location().warning(QStringLiteral(
"Missing comma in '\\%1'").arg(cmdName(
CMD_SA)));
1960 if (ch == QLatin1Char(
' ')) {
1961 if (!atom->string().endsWith(QLatin1Char(
' ')))
1962 atom->appendChar(QLatin1Char(
' '));
1964 atom->appendChar(ch);
1967void DocParser::appendWord(
const QString &word)
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987void DocParser::appendEscapedIdentifier()
1989 Q_ASSERT(m_position < m_inputLength);
1990 Q_ASSERT(m_input.at(m_position) ==
'\\');
1995 while (m_position < m_inputLength && m_input.at(m_position).isLetterOrNumber()) {
1996 identifier += m_input.at(m_position);
2001 if (!identifier.isEmpty()) {
2006 if (m_position + 1 < m_inputLength && m_input.at(m_position) ==
'\r' && m_input.at(m_position + 1) ==
'\n')
2009 appendChar(QLatin1Char(
'\\'));
2013void DocParser::appendToCode(
const QString &markedCode)
2015 if (!isCode(m_lastAtom)) {
2019 m_lastAtom->concatenateString(markedCode);
2023
2024
2025
2026
2027
2028void DocParser::appendToCode(
const QString &markedCode,
Atom::
AtomType defaultType,
const QString &language)
2030 if (!isCode(m_lastAtom)) {
2031 appendAtom(
Atom(defaultType, markedCode, language));
2034 m_lastAtom->concatenateString(markedCode);
2040 if (m_paragraphState != OutsideParagraph)
2051 const bool inValueList = !m_openedLists.isEmpty()
2052 && m_openedLists.top().style() == OpenedList::Value;
2056 if (!inValueList || !isAdmonition)
2060 appendAtom(
Atom(leftType, string));
2061 m_indexStartedParagraph =
false;
2062 m_pendingParagraphLeftType = leftType;
2063 m_pendingParagraphRightType = rightType;
2064 m_pendingParagraphString = string;
2066 m_paragraphState = InSingleLineParagraph;
2068 m_paragraphState = InBraceDelimitedParagraph;
2070 m_paragraphState = InMultiLineParagraph;
2072 skipSpacesOrOneEndl();
2077 if (m_paragraphState == OutsideParagraph)
2079 if (!m_pendingFormats.isEmpty() || (m_paragraphState == InBraceDelimitedParagraph && m_braceDepth > 0)) {
2080 location().warning(u"Missing '}'"_s);
2081 m_pendingFormats.clear();
2097 appendAtom(Atom(m_pendingParagraphRightType, m_pendingParagraphString));
2100 m_paragraphState = OutsideParagraph;
2101 m_indexStartedParagraph =
false;
2102 m_pendingParagraphRightType =
Atom::Nop;
2103 m_pendingParagraphString.clear();
2109 if (m_openedLists.isEmpty()) {
2110 m_openedLists.push(OpenedList(OpenedList::Value));
2122 if (!m_openedLists.isEmpty() && (m_openedLists.top().style() == OpenedList::Value)) {
2127 m_openedLists.pop();
2133 if (m_inTableItem) {
2136 m_inTableItem =
false;
2138 if (m_inTableHeader) {
2140 m_inTableHeader =
false;
2144 m_inTableRow =
false;
2149
2150
2151
2152
2166 auto maybe_resolved_file{(*file_resolver).resolve(filename)};
2167 if (!maybe_resolved_file) {
2176 QString details =
std::transform_reduce(
2177 (*file_resolver).get_search_directories().cbegin(),
2178 (*file_resolver).get_search_directories().cend(),
2179 u"Searched directories:"_s,
2181 [](
const DirectoryPath &directory_path) -> QString {
return u' ' + directory_path.value(); }
2184 location().warning(u"Cannot find file to quote from: %1"_s.arg(filename), details);
2201 marker = CodeMarker::markerForFileName(QString{});
2202 m_quoter.quoteFromFile(filename, QString{}, marker->markedUpCode(QString{},
nullptr, location()));
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232bool DocParser::expandMacro(ArgumentParsingOptions options)
2234 Q_ASSERT(m_input[m_position].unicode() ==
'\\');
2236 if (options == ArgumentParsingOptions::Verbatim)
2240 qsizetype backslashPos = m_position++;
2241 while (m_position < m_input.size() && m_input[m_position].isLetterOrNumber())
2242 cmdStr += m_input[m_position++];
2244 m_endPosition = m_position;
2245 if (!cmdStr.isEmpty()) {
2246 if (s_utilities
.macroHash.contains(cmdStr)) {
2248 if (!macro.m_defaultDef.isEmpty()) {
2249 QString expanded = expandMacroToString(cmdStr, macro);
2250 m_input.replace(backslashPos, m_position - backslashPos, expanded);
2251 m_inputLength = m_input.size();
2252 m_position = backslashPos;
2255 location().warning(
"Macro '%1' does not have a default definition"_L1.arg(cmdStr));
2259 m_position = backslashPos;
2260 if (options != ArgumentParsingOptions::MacroArguments
2262 location().warning(
"Unknown macro '%1'"_L1.arg(cmdStr));
2266 }
else if (m_input[m_position].isSpace()) {
2268 }
else if (m_input[m_position].unicode() ==
'\\') {
2270 m_input.remove(m_position--, 1);
2276void DocParser::expandMacro(
const QString &def,
const QStringList &args)
2278 if (args.isEmpty()) {
2283 for (
int j = 0; j < def.size(); ++j) {
2284 if (
int paramNo = def[j].unicode(); paramNo >= 1 && paramNo <= args.length()) {
2285 if (!rawString.isEmpty()) {
2291 rawString += def[j];
2294 if (!rawString.isEmpty())
2299QString
DocParser::expandMacroToString(
const QString &name,
const Macro ¯o)
2301 const QString &def{macro.m_defaultDef};
2305 rawString = macro.m_defaultDef;
2307 QStringList args{getMacroArguments(name, macro)};
2309 for (
int j = 0; j < def.size(); ++j) {
2310 int paramNo = def[j].unicode();
2311 rawString += (paramNo >= 1 && paramNo <= args.length()) ? args[paramNo - 1] : def[j];
2314 QString matchExpr{macro.m_otherDefs.value(
"match")};
2315 if (matchExpr.isEmpty())
2319 QRegularExpression re(matchExpr);
2320 int capStart = (re.captureCount() > 0) ? 1 : 0;
2322 QRegularExpressionMatch match;
2323 while ((match = re.match(rawString, i)).hasMatch()) {
2324 for (
int c = capStart; c <= re.captureCount(); ++c)
2325 result += match.captured(c);
2326 i = match.capturedEnd();
2334 QString name = getOptionalArgument();
2336 if (name ==
"section1") {
2338 }
else if (name ==
"section2") {
2340 }
else if (name ==
"section3") {
2342 }
else if (name ==
"section4") {
2344 }
else if (name.isEmpty()) {
2347 location().warning(QStringLiteral(
"Invalid section '%1'").arg(name));
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362QString
DocParser::getBracedArgument(ArgumentParsingOptions options)
2366 if (m_position < m_input.size() && m_input[m_position] ==
'{') {
2368 while (m_position < m_input.size() && delimDepth >= 0) {
2369 switch (m_input[m_position].unicode()) {
2372 arg += QLatin1Char(
'{');
2377 if (delimDepth >= 0)
2378 arg += QLatin1Char(
'}');
2382 if (!expandMacro(options))
2383 arg += m_input[m_position++];
2386 if (m_input[m_position].isSpace() && options != ArgumentParsingOptions::Verbatim)
2389 arg += m_input[m_position];
2394 location().warning(QStringLiteral(
"Missing '}'"));
2396 m_endPosition = m_position;
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416QString
DocParser::getArgument(ArgumentParsingOptions options)
2418 skipSpacesOrOneEndl();
2421 qsizetype startPos = m_position;
2422 QString arg = getBracedArgument(options);
2423 if (arg.isEmpty()) {
2424 while ((m_position < m_input.size())
2425 && ((delimDepth > 0) || ((delimDepth == 0) && !m_input[m_position].isSpace()))) {
2426 switch (m_input[m_position].unicode()) {
2431 arg += m_input[m_position];
2438 if (m_position == startPos || delimDepth >= 0) {
2439 arg += m_input[m_position];
2444 if (!expandMacro(options))
2445 arg += m_input[m_position++];
2448 arg += m_input[m_position];
2452 m_endPosition = m_position;
2453 if ((arg.size() > 1) && (QString(
".,:;!?").indexOf(m_input[m_position - 1]) != -1)
2454 && !arg.endsWith(
"...")) {
2455 arg.truncate(arg.size() - 1);
2458 if (arg.size() > 2 && m_input.mid(m_position - 2, 2) ==
"'s") {
2459 arg.truncate(arg.size() - 2);
2463 return arg.simplified();
2467
2468
2469
2470
2471
2472QString
DocParser::getBracketedArgument()
2476 skipSpacesOrOneEndl();
2477 if (m_position < m_input.size() && m_input[m_position] ==
'[') {
2479 while (m_position < m_input.size() && delimDepth >= 0) {
2480 switch (m_input[m_position].unicode()) {
2483 arg += QLatin1Char(
'[');
2488 if (delimDepth >= 0)
2489 arg += QLatin1Char(
']');
2493 arg += m_input[m_position];
2497 arg += m_input[m_position];
2502 location().warning(QStringLiteral(
"Missing ']'"));
2508
2509
2510
2511
2512
2513
2514
2518 if (isLeftBracketAhead(0)) {
2519 value = getBracketedArgument();
2520 *marker = markerForLanguage(value);
2525 return value.toLower();
2529
2530
2531
2532
2533
2534QStringList
DocParser::getMacroArguments(
const QString &name,
const Macro ¯o)
2538 if (macro
.numParams == 1 || isLeftBraceAhead()) {
2539 args << getArgument(ArgumentParsingOptions::MacroArguments);
2541 location().warning(QStringLiteral(
"Macro '\\%1' invoked with too few"
2542 " arguments (expected %2, got %3)")
2553
2554
2555
2556
2559 skipSpacesOrOneEndl();
2560 const bool nextIsCommand = m_position + 1 < m_input.size() && m_input[m_position] ==
'\\'
2561 && m_input[m_position + 1].isLetterOrNumber();
2562 const bool nextIsComment = m_position + 2 < m_input.size() && m_input[m_position] ==
'/'
2563 && m_input[m_position + 1] ==
'/' && m_input[m_position + 2] ==
'!';
2564 if (nextIsCommand || nextIsComment) {
2567 return getArgument();
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2588 auto atLineEnd = [
this]() ->
bool {
2589 return m_position >= m_inputLength || m_input[m_position] ==
'\n'
2590 || m_input[m_position] ==
'\r';
2593 auto lineHasTrailingBackslash = [
this, &atLineEnd](
bool trailingBackslash) ->
bool {
2594 while (!atLineEnd()) {
2595 if (m_input[m_position] ==
'\\' && !trailingBackslash) {
2596 trailingBackslash =
true;
2600 trailingBackslash =
false;
2604 return trailingBackslash;
2607 QString rest_of_line;
2609 bool trailing_backslash{
false };
2610 bool return_simplified_string{
false };
2612 for (qsizetype start_position = m_position; m_position < m_inputLength; ++m_position) {
2613 trailing_backslash = lineHasTrailingBackslash(trailing_backslash);
2615 if (!rest_of_line.isEmpty())
2616 rest_of_line += QLatin1Char(
' ');
2617 rest_of_line += m_input.sliced(start_position, m_position - start_position);
2619 if (trailing_backslash) {
2620 rest_of_line.truncate(rest_of_line.lastIndexOf(
'\\'));
2621 return_simplified_string =
true;
2624 if (m_position < m_inputLength && m_input[m_position] ==
'\r')
2626 if (m_position < m_inputLength && m_input[m_position] ==
'\n')
2629 if (!trailing_backslash)
2632 while (m_position < m_inputLength
2633 && (m_input[m_position] ==
'\r' || m_input[m_position] ==
'\n'))
2636 start_position = m_position;
2639 if (return_simplified_string)
2640 return rest_of_line.simplified();
2642 return rest_of_line.trimmed();
2646
2647
2648
2649
2650QString
DocParser::getMetaCommandArgument(
const QString &cmdStr)
2654 qsizetype begin = m_position;
2657 while (m_position < m_input.size() && (m_input[m_position] !=
'\n' || parenDepth > 0)) {
2658 if (m_input.at(m_position) ==
'(')
2660 else if (m_input.at(m_position) ==
')')
2662 else if (m_input.at(m_position) ==
'\\' && expandMacro(ArgumentParsingOptions::Default))
2666 if (m_position == m_input.size() && parenDepth > 0) {
2668 location().warning(QStringLiteral(
"Unbalanced parentheses in '%1'").arg(cmdStr));
2671 QString t = m_input.mid(begin, m_position - begin).simplified();
2679 QRegularExpression rx(
"\\\\" + cmdName(endCmd) +
"\\b");
2681 auto match = rx.match(m_input, m_position);
2683 if (!match.hasMatch()) {
2684 location().warning(QStringLiteral(
"Missing '\\%1'").arg(cmdName(endCmd)));
2685 m_position = m_input.size();
2687 qsizetype end = match.capturedStart();
2688 t = m_input.mid(m_position, end - m_position);
2689 m_position = match.capturedEnd();
2694void DocParser::expandArgumentsInString(QString &str,
const QStringList &args)
2701 while (j < str.size()) {
2702 if (str[j] ==
'\\' && j < str.size() - 1 && (paramNo = str[j + 1].digitValue()) >= 1
2703 && paramNo <= args.size()) {
2704 const QString &r = args[paramNo - 1];
2705 str.replace(j, 2, r);
2706 j += qMin(1, r.size());
2714
2715
2716
2717
2718
2719
2722 QString code = untabifyEtc(getUntilEnd(cmd));
2723 expandArgumentsInString(code, argStr.split(
" ", Qt::SkipEmptyParts));
2725 int indent = indentLevel(code);
2726 code = dedent(indent, code);
2729 if (!marker && !m_private->m_topics.isEmpty()
2730 && m_private->m_topics[0].m_topic.startsWith(
"qml")) {
2731 auto qmlMarker = CodeMarker::markerForLanguage(
"QML");
2732 marker = (qmlMarker && qmlMarker->recognizeCode(code)) ? qmlMarker :
nullptr;
2734 if (marker ==
nullptr)
2735 marker = CodeMarker::markerForCode(code);
2736 return marker->markedUpCode(code,
nullptr, location());
2741 qsizetype i = m_position;
2743 while (i < m_inputLength && m_input[i].isSpace()) {
2744 if (m_input[i] ==
'\n')
2754 qsizetype i = m_position;
2756 while (i < m_inputLength && m_input[i].isSpace() && numEndl < 2) {
2758 if (m_input[i] ==
'\n')
2762 return numEndl < 2 && i < m_inputLength && m_input[i] ==
'{';
2765bool DocParser::isLeftBracketAhead(
int maxNewlines)
2768 qsizetype i = m_position;
2770 while (i < m_inputLength && m_input[i].isSpace() && numEndl < 2) {
2772 if (m_input[i] ==
'\n')
2776 return numEndl <= maxNewlines && i < m_inputLength && m_input[i] ==
'[';
2780
2781
2784 while ((m_position < m_input.size()) && m_input[m_position].isSpace()
2785 && (m_input[m_position].unicode() !=
'\n'))
2790
2791
2794 qsizetype firstEndl = -1;
2795 while (m_position < m_input.size() && m_input[m_position].isSpace()) {
2796 QChar ch = m_input[m_position];
2798 if (firstEndl == -1) {
2799 firstEndl = m_position;
2801 m_position = firstEndl;
2811 while (m_position < m_inputLength && m_input[m_position].isSpace())
2815void DocParser::skipToNextPreprocessorCommand()
2817 QRegularExpression rx(
"\\\\(?:" + cmdName(CMD_IF) + QLatin1Char(
'|') + cmdName(CMD_ELSE)
2818 + QLatin1Char(
'|') + cmdName(CMD_ENDIF) +
")\\b");
2819 auto match = rx.match(m_input, m_position + 1);
2821 if (!match.hasMatch())
2822 m_position = m_input.size();
2824 m_position = match.capturedStart();
2877 return cmds[cmd]
.name;
2882 return cmdName(endCmdFor(cmd));
2888 result.reserve(str.size());
2891 for (
const auto &character : str) {
2892 if (character == QLatin1Char(
'\r'))
2894 if (character == QLatin1Char(
'\t')) {
2895 result += &
" "[column % s_tabSize];
2896 column = ((column / s_tabSize) + 1) * s_tabSize;
2899 if (character == QLatin1Char(
'\n')) {
2900 while (result.endsWith(QLatin1Char(
' ')))
2902 result += character;
2906 result += character;
2910 while (result.endsWith(
"\n\n"))
2911 result.truncate(result.size() - 1);
2912 while (result.startsWith(QLatin1Char(
'\n')))
2913 result = result.mid(1);
2920 int minIndent = INT_MAX;
2923 for (
const auto &character : str) {
2924 if (character ==
'\n') {
2927 if (character !=
' ' && column < minIndent)
2943 for (
const auto &character : str) {
2944 if (character == QLatin1Char(
'\n')) {
2948 if (column >= level)
2949 result += character;
2957
2958
2966
2967
2977
2978
2979
2980
2983 CodeMarker *marker = CodeMarker::markerForLanguage(language.toLower());
2985 if (!marker && !s_allowedLanguages.contains(language, Qt::CaseInsensitive))
2986 location().warning(QStringLiteral(
"Unrecognized markup language '%1'").arg(language));
2992
2993
2994
2995
2996
2997
2998
2999
3002 static auto take_while = [](QStringView input,
auto predicate) {
3003 QStringView::size_type end{0};
3005 while (end < input.size() &&
std::invoke(predicate, input[end]))
3008 return std::make_tuple(input.sliced(0, end), input.sliced(end));
3011 static auto peek = [](QStringView input, QChar c) {
3012 return !input.empty() && input.first() == c;
3015 static auto skip_one = [](QStringView input) {
3016 if (input.empty())
return std::make_tuple(QStringView{}, input);
3017 else return std::make_tuple(input.sliced(0, 1), input.sliced(1));
3020 static auto enclosed = [](QStringView input, QChar open, QChar close) {
3021 if (!peek(input, open))
return std::make_tuple(QStringView{}, input);
3023 auto [opened, without_open] = skip_one(input);
3024 auto [parsed, remaining] = take_while(without_open, [close](QChar c){
return c != close; });
3026 if (remaining.empty())
return std::make_tuple(QStringView{}, input);
3028 auto [closed, without_close] = skip_one(remaining);
3030 return std::make_tuple(parsed.trimmed(), without_close);
3033 static auto one_of = [](
auto first,
auto second) {
3034 return [first, second](QStringView input) {
3035 auto [parsed, remaining] =
std::invoke(first, input);
3037 if (parsed.empty())
return std::invoke(second, input);
3038 else return std::make_tuple(parsed, remaining);
3042 static auto collect = [](QStringView input,
auto parser) {
3043 QStringList collected{};
3046 auto [parsed, remaining] =
std::invoke(parser, input);
3048 if (parsed.empty())
break;
3049 collected.append(parsed.toString());
3057 static auto spaces = [](QStringView input) {
3058 return take_while(input, [](QChar c){
return c.isSpace(); });
3061 static auto word = [](QStringView input) {
3062 return take_while(input, [](QChar c){
return !c.isSpace(); });
3065 static auto parse_argument = [](QStringView input) {
3066 auto [_, without_spaces] = spaces(input);
3069 [](QStringView input){
return enclosed(input,
'{',
'}'); },
3074 const QString cmd{DocParser::cmdName(CMD_COMPARESWITH)};
3078 QStringList segments = collect(atom->string(), parse_argument);
3080 QString categoryString;
3081 if (!segments.isEmpty())
3082 categoryString = segments.takeFirst();
3083 auto category = comparisonCategoryFromString(categoryString.toStdString());
3085 if (category == ComparisonCategory::None) {
3086 location.warning(u"Invalid argument to \\%1 command: `%2`"_s.arg(cmd, categoryString),
3087 u"Valid arguments are `strong`, `weak`, `partial`, or `equality`."_s);
3091 if (segments.isEmpty()) {
3092 location.warning(u"Missing argument to \\%1 command."_s.arg(cmd),
3093 u"Provide at least one type name, or a list of types separated by spaces."_s);
3098 segments.removeDuplicates();
3099 atom->setString(segments.join(QLatin1Char(
';')));
3103 priv->m_metacommandsUsed.insert(cmd);
3106 const auto end{priv
->extra->m_comparesWithMap.cend()};
3107 priv
->extra->m_comparesWithMap.insert(end, category, text);
#define ATOM_FORMATTING_TELETYPE
#define ATOM_FORMATTING_UNDERLINE
#define ATOM_FORMATTING_NOTRANSLATE
#define ATOM_FORMATTING_SPAN
#define ATOM_FORMATTING_SUBSCRIPT
#define ATOM_FORMATTING_BOLD
#define ATOM_FORMATTING_TRADEMARK
#define ATOM_FORMATTING_ITALIC
#define ATOM_FORMATTING_LINK
#define ATOM_FORMATTING_SUPERSCRIPT
#define ATOM_FORMATTING_INDEX
#define ATOM_FORMATTING_UICONTROL
#define ATOM_FORMATTING_PARAMETER
The Atom class is the fundamental unit for representing documents internally.
AtomType type() const
Return the type of this atom.
AtomType
\value AnnotatedList \value AutoLink \value BaseName \value BriefLeft \value BriefRight \value C \val...
void chopString()
\also string()
virtual Atom::AtomType atomType() const
The Config class contains the configuration variables for controlling how qdoc produces documentation...
static QStringList s_allowedLanguages
static QStringList s_ignoreWords
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 int endCmdFor(int cmd)
static void initialize(const Config &config, FileResolver &file_resolver)
void addAlso(const Text &also)
CommandMap m_metaCommandMap
static void quoteFromFile(const Location &location, Quoter "er, ResolvedFile resolved_file, CodeMarker *marker=nullptr)
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.
static Text subText(const Atom *begin, const Atom *end=nullptr)
Text splitAtFirst(Atom::AtomType start)
Splits the current Text from start to end into a new Text object.
Text & operator=(const Text &text)
#define CONFIG_IGNOREWORDS
#define CONFIG_CODELANGUAGES
#define CONFIG_QUOTINGINFORMATION
std::pair< QString, QString > ArgPair
static void warnAboutEmptyOrPreexistingTarget(const Location &location, const QString &duplicateDefinition, const QString &cmdString, const QString &previousDefinition)
bool is_formatting_command
static void processComparesWithCommand(DocPrivate *priv, const Location &location)
static QString cleanLink(const QString &link)
Returns a cleaned version of the given link text.
Combined button and popup list for selecting options.
QHash_QString_Macro macroHash
QHash_QString_int cmdHash
Simple structure used by the Doc and DocParser classes.