13#include <QtCore/qfile.h>
14#include <QtCore/qregularexpression.h>
15#include <QtCore/qtextstream.h>
23using namespace Qt::StringLiterals;
134} cmds[] = { {
"a",
CMD_A,
true },
136 {
"b",
CMD_B,
true },
141 {
"c",
CMD_C,
true },
149 {
"e",
CMD_E,
true },
174 {
"i",
CMD_I,
true },
242 qsizetype colonPos = link.indexOf(
':');
243 if ((colonPos == -1) || (!link.startsWith(
"file:") && !link.startsWith(
"mailto:")))
245 return link.mid(colonPos + 1).simplified();
254 while (cmds[i]
.name) {
258 Location::internalError(QStringLiteral(
"command %1 missing").arg(i));
264 const auto &outputFormats = config.getOutputFormats();
265 for (
const auto &format : outputFormats)
266 DocParser::s_quoting = DocParser::s_quoting
271 DocParser::file_resolver = &file_resolver;
275
276
277
278
279
280
281
282
284 const QSet<QString> &metaCommandSet,
const QSet<QString> &possibleTopics)
288 m_inputLength = m_input.size();
289 m_cachedLocation = docPrivate->m_start_loc;
290 m_cachedPosition = 0;
291 m_private = docPrivate;
295 m_paragraphState = OutsideParagraph;
296 m_inTableHeader =
false;
297 m_inTableRow =
false;
298 m_inTableItem =
false;
299 m_indexStartedParagraph =
false;
305 m_openedCommands.push(CMD_OMIT);
309 Atom *currentLinkAtom =
nullptr;
311 QStack<
bool> preprocessorSkipping;
312 int numPreprocessorSkipping = 0;
314 while (m_position < m_inputLength) {
315 QChar ch = m_input.at(m_position);
317 switch (ch.unicode()) {
320 m_backslashPosition = m_position;
322 while (m_position < m_inputLength) {
323 ch = m_input.at(m_position);
324 if (ch.isLetterOrNumber()) {
331 m_endPosition = m_position;
332 if (cmdStr.isEmpty()) {
333 if (m_position < m_inputLength) {
335 if (m_input.at(m_position).isSpace()) {
337 appendChar(QLatin1Char(
' '));
339 appendChar(m_input.at(m_position++));
356 m_private->m_params.insert(p1);
360 appendAtom(Atom(Atom::CodeBad,
361 getCode(CMD_BADCODE, marker, getMetaCommandArgument(cmdStr))));
368 location().warning(QStringLiteral(
"'\\bold' is deprecated. Use '\\b'"));
375 enterPara(Atom::BriefLeft, Atom::BriefRight);
379 p1 = untabifyEtc(getArgument(ArgumentParsingOptions::Verbatim));
380 marker = CodeMarker::markerForCode(p1);
381 appendAtom(
Atom(
Atom::C, marker->markedUpCode(p1,
nullptr, location())));
385 enterPara(Atom::CaptionLeft, Atom::CaptionRight);
389 appendAtom(Atom(Atom::Code, getCode(CMD_CODE,
nullptr, getMetaCommandArgument(cmdStr))));
393 appendAtom(Atom(Atom::Qml,
394 getCode(CMD_QML, CodeMarker::markerForLanguage(QLatin1String(
"QML")),
395 getMetaCommandArgument(cmdStr))));
399 appendAtom(Atom(Atom::DetailsLeft, getArgument()));
400 m_openedCommands.push(cmd);
409 p1 = getArgument(ArgumentParsingOptions::Verbatim);
411 m_openedCommands.push(cmd);
423 if (isCode(m_lastAtom) && m_lastAtom->string().endsWith(
"\n\n"))
428 QString arg = getOptionalArgument();
435 if (isCode(m_lastAtom) && m_lastAtom->string().endsWith(
"\n\n"))
438 int indent = arg.toInt();
439 for (
int i = 0; i < indent; ++i)
441 appendToCode(
"...\n");
445 if (!preprocessorSkipping.empty()) {
446 if (preprocessorSkipping.top()) {
447 --numPreprocessorSkipping;
449 ++numPreprocessorSkipping;
451 preprocessorSkipping.top() = !preprocessorSkipping.top();
452 (
void)getRestOfLine();
453 if (numPreprocessorSkipping)
454 skipToNextPreprocessorCommand();
457 QStringLiteral(
"Unexpected '\\%1'").arg(cmdName(
CMD_ELSE)));
467 if (closeCommand(cmd)) {
473 if (preprocessorSkipping.size() > 0) {
474 if (preprocessorSkipping.pop())
475 --numPreprocessorSkipping;
476 (
void)getRestOfLine();
477 if (numPreprocessorSkipping)
478 skipToNextPreprocessorCommand();
481 QStringLiteral(
"Unexpected '\\%1'").arg(cmdName(
CMD_ENDIF)));
485 if (closeCommand(cmd)) {
491 if (closeCommand(cmd)) {
499 if (closeCommand(cmd)) {
501 if (m_openedLists.top().isStarted()) {
502 appendAtom(Atom(Atom::ListItemRight, m_openedLists.top().styleString()));
503 appendAtom(Atom(Atom::ListRight, m_openedLists.top().styleString()));
512 if (closeCommand(cmd)) {
519 QStringLiteral(
"Unexpected '\\%1'").arg(cmdName(
CMD_ENDRAW)));
534 if (closeCommand(cmd)) {
540 if (closeCommand(cmd)) {
546 if (openCommand(cmd)) {
553 if (isLeftBracketAhead())
554 p2 = getBracketedArgument();
557 appendAtom(Atom(Atom::AnnotatedList, getArgument(), p2));
561 appendAtom(Atom(Atom::SinceList, getRestOfLine().simplified()));
565 if (isLeftBracketAhead())
566 p2 = getBracketedArgument();
569 QString arg1 = getArgument();
570 QString arg2 = getOptionalArgument();
573 appendAtom(Atom(Atom::GeneratedList, arg1, p2));
576 if (m_openedCommands.top() == CMD_TABLE) {
579 m_inTableHeader =
true;
581 if (m_openedCommands.contains(CMD_TABLE))
582 location().warning(QStringLiteral(
"Cannot use '\\%1' within '\\%2'")
583 .arg(cmdName(CMD_HEADER),
584 cmdName(m_openedCommands.top())));
587 QStringLiteral(
"Cannot use '\\%1' outside of '\\%2'")
592 location().warning(QStringLiteral(
593 "'\\i' is deprecated. Use '\\e' for italic or '\\li' for list item"));
603 preprocessorSkipping.push(!Tokenizer::isTrue(getRestOfLine()));
604 if (preprocessorSkipping.top())
605 ++numPreprocessorSkipping;
606 if (numPreprocessorSkipping)
607 skipToNextPreprocessorCommand();
611 appendAtom(Atom(Atom::Image, getArgument()));
612 appendAtom(Atom(Atom::ImageText, getRestOfLine()));
616 enterPara(Atom::ImportantLeft, Atom::ImportantRight);
620 QString fileName = getArgument();
621 QStringList parameters;
623 if (isLeftBraceAhead()) {
624 identifier = getArgument();
625 while (isLeftBraceAhead() && parameters.size() < 9)
626 parameters << getArgument();
628 identifier = getRestOfLine();
630 include(fileName, identifier, parameters);
633 case CMD_INLINEIMAGE:
635 appendAtom(Atom(Atom::InlineImage, getArgument()));
638 if (isLeftBraceAhead()) {
639 appendAtom(Atom(Atom::ImageText, getArgument()));
644 if (m_paragraphState == OutsideParagraph) {
646 m_indexStartedParagraph =
true;
649 if (m_indexStartedParagraph
652 m_indexStartedParagraph =
false;
658 insertKeyword(getRestOfLine());
662 if (isLeftBracketAhead())
663 p2 = getBracketedArgument();
667 appendAtom(LinkAtom(p1, p2, location()));
669 if (isLeftBraceAhead()) {
674 appendAtom(Atom(Atom::String, cleanLink(p1)));
683 if (openCommand(cmd))
688 if (openCommand(cmd)) {
693 skipSpacesOrOneEndl();
697 if (openCommand(cmd)) {
699 m_openedLists.push(OpenedList(location(), getOptionalArgument()));
705 m_private->extra->m_metaMap.insert(p1, getArgument());
709 enterPara(Atom::NoteLeft, Atom::NoteRight);
712 location().warning(QStringLiteral(
"'\\o' is deprecated. Use '\\li'"));
716 if (m_openedCommands.top() == CMD_LIST) {
717 if (m_openedLists.top().isStarted())
718 appendAtom(Atom(Atom::ListItemRight, m_openedLists.top().styleString()));
720 appendAtom(Atom(Atom::ListLeft, m_openedLists.top().styleString()));
721 m_openedLists.top().next();
722 appendAtom(Atom(Atom::ListItemNumber, m_openedLists.top().numberString()));
723 appendAtom(Atom(Atom::ListItemLeft, m_openedLists.top().styleString()));
725 }
else if (m_openedCommands.top() == CMD_TABLE) {
728 if (isLeftBraceAhead()) {
730 if (isLeftBraceAhead())
734 if (!m_inTableHeader && !m_inTableRow) {
736 QStringLiteral(
"Missing '\\%1' or '\\%2' before '\\%3'")
741 }
else if (m_inTableItem) {
743 m_inTableItem =
false;
746 appendAtom(Atom(Atom::TableItemLeft, p1, p2));
747 m_inTableItem =
true;
750 QStringLiteral(
"Command '\\%1' outside of '\\%2' and '\\%3'")
759 if (!m_private->m_enumItemList.contains(p1))
760 m_private->m_enumItemList.append(p1);
761 if (!m_private->m_omitEnumItemList.contains(p1))
762 m_private->m_omitEnumItemList.append(p1);
763 skipSpacesOrOneEndl();
765 while (m_position < m_inputLength && !isBlankLine()) {
767 if (qsizetype pos = m_position; pos < m_input.size()
768 && m_input.at(pos++).unicode() ==
'\\') {
770 while (pos < m_input.size() && m_input[pos].isLetterOrNumber())
771 nextCmdStr += m_input[pos++];
773 if (nextCmd == cmd || nextCmd ==
CMD_VALUE)
782 p1 = getRestOfLine();
783 if (openCommand(cmd))
787 if (closeCommand(cmd)) {
795 QString rest = getRestOfLine();
800 appendToCode(m_quoter.quoteLine(location(), cmdStr, rest));
805 QString rest = getRestOfLine();
810 appendToCode(m_quoter.quoteTo(location(), cmdStr, rest));
815 QString rest = getRestOfLine();
820 appendToCode(m_quoter.quoteUntil(location(), cmdStr, rest));
824 if (openCommand(cmd)) {
832 QString fileName = getArgument();
833 quoteFromFile(fileName);
838 appendAtom(Atom(Atom::Code, m_quoter.quoteTo(location(), cmdStr, QString())));
844 QString arg = getArgument();
854 p1 = getRestOfLine();
856 location().warning(QStringLiteral(
"Missing format name after '\\%1'")
859 appendAtom(Atom(Atom::RawString, untabifyEtc(getUntilEnd(cmd))));
864 if (m_openedCommands.top() == CMD_TABLE) {
866 if (isLeftBraceAhead())
867 p1 = getArgument(ArgumentParsingOptions::Verbatim);
872 if (m_openedCommands.contains(CMD_TABLE))
873 location().warning(QStringLiteral(
"Cannot use '\\%1' within '\\%2'")
874 .arg(cmdName(CMD_ROW),
875 cmdName(m_openedCommands.top())));
877 location().warning(QStringLiteral(
"Cannot use '\\%1' outside of '\\%2'")
897 if (openCommand(cmd)) {
904 QString rest = getRestOfLine();
909 m_quoter.quoteLine(location(), cmdStr, rest);
914 QString rest = getRestOfLine();
919 m_quoter.quoteTo(location(), cmdStr, rest);
924 QString rest = getRestOfLine();
929 m_quoter.quoteUntil(location(), cmdStr, rest);
934 startFormat(p1, cmd);
938 QString snippet = getArgument();
939 QString identifier = getRestOfLine();
945 marker = CodeMarker::markerForFileName(snippet);
946 quoteFromFile(snippet);
947 appendToCode(m_quoter.quoteSnippet(location(), identifier), marker->atomType());
958 p1 = getOptionalArgument();
959 p2 = getOptionalArgument();
960 if (openCommand(cmd)) {
962 appendAtom(Atom(Atom::TableLeft, p1, p2));
963 m_inTableHeader =
false;
964 m_inTableRow =
false;
965 m_inTableItem =
false;
970 if (isLeftBraceAhead())
972 p1 += QLatin1Char(
',');
973 p1 += QString::number((
int)getSectioningUnit());
977 if (m_openedCommands.top() == CMD_TABLE && !m_inTableItem) {
978 location().warning(
"Found a \\target command outside table item in a table.\n"
979 "Move the \\target inside the \\li to resolve this warning.");
981 insertTarget(getRestOfLine());
985 if (m_paragraphState != InSingleLineParagraph)
1001 uint unicodeChar = p1.toUInt(&ok, 0);
1002 if (!ok || (unicodeChar == 0x0000) || (unicodeChar > 0xFFFE))
1004 QStringLiteral(
"Invalid Unicode character '%1' specified with '%2'")
1012 if (m_openedLists.top().style() == OpenedList::Value) {
1015 if (p1.startsWith(QLatin1String(
"[since "))
1016 && p1.endsWith(QLatin1String(
"]"))) {
1017 p2 = p1.mid(7, p1.size() - 8);
1020 if (!m_private->m_enumItemList.contains(p1))
1021 m_private->m_enumItemList.append(p1);
1023 m_openedLists.top().next();
1027 if (!p2.isEmpty()) {
1034 skipSpacesOrOneEndl();
1043 enterPara(Atom::WarningLeft, Atom::WarningRight);
1047 m_private->m_metacommandsUsed.insert(cmdStr);
1050 p1 = getRestOfLine();
1051 if (!p1.isEmpty()) {
1053 appendAtom(Atom(
Atom::String,
"This function overloads "));
1059 appendAtom(Atom(
Atom::String,
"This is an overloaded function."));
1061 p1 = getMetaCommandArgument(cmdStr);
1063 m_private->m_metaCommandMap[cmdStr].append(
ArgPair(p1, QString()));
1066 if (metaCommandSet.contains(cmdStr)) {
1068 QString bracketedArg;
1069 m_private->m_metacommandsUsed.insert(cmdStr);
1070 if (isLeftBracketAhead())
1071 bracketedArg = getBracketedArgument();
1074 if (m_position < m_inputLength
1075 && (cmdStr == QLatin1String(
"obsolete")
1076 || cmdStr == QLatin1String(
"deprecated")))
1077 m_input[m_position] =
'\n';
1079 arg = getMetaCommandArgument(cmdStr);
1080 m_private->m_metaCommandMap[cmdStr].append(
ArgPair(arg, bracketedArg));
1081 if (possibleTopics.contains(cmdStr)) {
1082 if (!cmdStr.endsWith(QLatin1String(
"propertygroup")))
1085 }
else if (s_utilities
.macroHash.contains(cmdStr)) {
1087 QStringList macroArgs;
1088 int numPendingFi = 0;
1089 int numFormatDefs = 0;
1090 for (
auto it = macro.m_otherDefs.constBegin();
1091 it != macro.m_otherDefs.constEnd(); ++it) {
1092 if (it.key() !=
"match") {
1093 if (numFormatDefs == 0)
1094 macroArgs = getMacroArguments(cmdStr, macro);
1096 expandMacro(*it, macroArgs);
1098 if (it == macro.m_otherDefs.constEnd()) {
1106 while (numPendingFi-- > 0)
1109 if (!macro.m_defaultDef.isEmpty()) {
1110 if (numFormatDefs > 0) {
1111 macro.m_defaultDefLocation.warning(
1112 QStringLiteral(
"Macro cannot have both "
1113 "format-specific and qdoc-"
1114 "syntax definitions"));
1116 QString expanded = expandMacroToString(cmdStr, macro);
1117 m_input.replace(m_backslashPosition,
1118 m_endPosition - m_backslashPosition, expanded);
1119 m_inputLength = m_input.size();
1120 m_position = m_backslashPosition;
1123 }
else if (isAutoLinkString(cmdStr)) {
1126 if (!cmdStr.endsWith(
"propertygroup")) {
1129 location().warning(QStringLiteral(
"Unknown command '\\%1'").arg(cmdStr),
1130 detailsUnknownCommand(metaCommandSet, cmdStr));
1141 qsizetype dashCount = 1;
1145 while ((m_position < m_inputLength) && (m_input.at(m_position) ==
'-')) {
1150 if (dashCount == 3) {
1152 const QChar emDash(8212);
1154 }
else if (dashCount == 2) {
1156 const QChar enDash(8211);
1162 for (qsizetype i = 0; i < dashCount; ++i)
1177 auto format = m_pendingFormats.find(m_braceDepth);
1178 if (format == m_pendingFormats.end()) {
1185 if (m_indexStartedParagraph)
1190 if (currentLinkAtom && currentLinkAtom->string().endsWith(
"::")) {
1194 currentLinkAtom->concatenateString(suffix);
1196 currentLinkAtom =
nullptr;
1200 m_pendingFormats.erase(format);
1206 if (m_position + 2 < m_inputLength)
1207 if (m_input.at(m_position + 1) ==
'/')
1208 if (m_input.at(m_position + 2) ==
'!') {
1211 if (m_input.at(m_position - 1) ==
'\n')
1227 if (m_paragraphState == OutsideParagraph) {
1239 && (m_paragraphState == InSingleLineParagraph || isBlankLine())) {
1252 qsizetype startPos = m_position;
1254 bool autolink = (!m_pendingFormats.isEmpty() &&
1256 false : isAutoLinkString(m_input, m_position);
1257 if (m_position == startPos) {
1258 if (!ch.isSpace()) {
1263 QString word = m_input.mid(startPos, m_position - startPos);
1265 if (s_ignoreWords.contains(word) || word.startsWith(QString(
"__")))
1280 if (m_openedCommands.top() == CMD_LEGALESE) {
1282 m_openedCommands.pop();
1285 if (m_openedCommands.top() != CMD_OMIT) {
1287 QStringLiteral(
"Missing '\\%1'").arg(endCmdName(m_openedCommands.top())));
1288 }
else if (preprocessorSkipping.size() > 0) {
1289 location().warning(QStringLiteral(
"Missing '\\%1'").arg(cmdName(
CMD_ENDIF)));
1293 appendAtom(Atom(Atom::SectionRight, QString::number(m_currentSection)));
1301
1302
1305 while (!m_openedInputs.isEmpty() && m_openedInputs.top() <= m_position) {
1306 m_cachedLocation.pop();
1307 m_cachedPosition = m_openedInputs.pop();
1309 while (m_cachedPosition < m_position)
1310 m_cachedLocation.advance(m_input.at(m_cachedPosition++));
1311 return m_cachedLocation;
1314QString
DocParser::detailsUnknownCommand(
const QSet<QString> &metaCommandSet,
const QString &str)
1316 QSet<QString> commandSet = metaCommandSet;
1318 while (cmds[i]
.name !=
nullptr) {
1319 commandSet.insert(cmds[i]
.name);
1323 QString best = nearestName(str, commandSet);
1326 return QStringLiteral(
"Maybe you meant '\\%1'?").arg(best);
1330
1331
1332
1333
1334
1335
1336
1338 const QString &cmdString,
const QString &previousDefinition)
1340 if (duplicateDefinition.isEmpty()) {
1341 location.warning(
"Expected an argument for \\%1"_L1.arg(cmdString));
1344 "Duplicate %3 name '%1'. The previous occurrence is here: %2"_L1
1345 .arg(duplicateDefinition, previousDefinition, cmdString));
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363void DocParser::insertTarget(
const QString &target)
1365 if (target.isEmpty() || m_targetMap.contains(target))
1366 return warnAboutEmptyOrPreexistingTarget(location(), target,
1367 s_utilities.cmdHash.key(CMD_TARGET), m_targetMap[target].toString());
1369 m_targetMap.insert(target, location());
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390void DocParser::insertKeyword(
const QString &keyword)
1392 if (keyword.isEmpty() || m_targetMap.contains(keyword))
1393 return warnAboutEmptyOrPreexistingTarget(location(), keyword,
1394 s_utilities.cmdHash.key(CMD_KEYWORD), m_targetMap[keyword].toString());
1396 m_targetMap.insert(keyword, location());
1403void DocParser::include(
const QString &fileName,
const QString &identifier,
const QStringList ¶meters)
1406 location().fatal(QStringLiteral(
"Too many nested '\\%1's").arg(cmdName(
CMD_INCLUDE)));
1407 QString filePath = Config::instance().getIncludeFilePath(fileName);
1408 if (filePath.isEmpty()) {
1409 location().warning(QStringLiteral(
"Cannot find qdoc include file '%1'").arg(fileName));
1411 QFile inFile(filePath);
1412 if (!inFile.open(QFile::ReadOnly)) {
1414 QStringLiteral(
"Cannot open qdoc include file '%1'").arg(filePath));
1416 location().push(fileName);
1417 QTextStream inStream(&inFile);
1418 QString includedContent = inStream.readAll();
1421 if (identifier.isEmpty()) {
1422 expandArgumentsInString(includedContent, parameters);
1423 m_input.insert(m_position, includedContent);
1424 m_inputLength = m_input.size();
1425 m_openedInputs.push(m_position + includedContent.size());
1427 QStringList lineBuffer = includedContent.split(QLatin1Char(
'\n'));
1428 qsizetype bufLen{lineBuffer.size()};
1430 QStringView trimmedLine;
1431 for (i = 0; i < bufLen; ++i) {
1432 trimmedLine = QStringView{lineBuffer[i]}.trimmed();
1433 if (trimmedLine.startsWith(QLatin1String(
"//!")) &&
1434 trimmedLine.contains(identifier))
1437 if (i < bufLen - 1) {
1441 QStringLiteral(
"Cannot find '%1' in '%2'").arg(identifier, filePath));
1446 trimmedLine = QStringView{lineBuffer[i]}.trimmed();
1447 if (trimmedLine.startsWith(QLatin1String(
"//!")) &&
1448 trimmedLine.contains(identifier))
1451 result += lineBuffer[i] + QLatin1Char(
'\n');
1453 }
while (i < bufLen);
1455 expandArgumentsInString(result, parameters);
1456 if (result.isEmpty()) {
1457 location().warning(QStringLiteral(
"Empty qdoc snippet '%1' in '%2'")
1458 .arg(identifier, filePath));
1460 m_input.insert(m_position, result);
1461 m_inputLength = m_input.size();
1462 m_openedInputs.push(m_position + result.size());
1469void DocParser::startFormat(
const QString &format,
int cmd)
1473 for (
const auto &item : std::as_const(m_pendingFormats)) {
1474 if (item == format) {
1475 location().warning(QStringLiteral(
"Cannot nest '\\%1' commands").arg(cmdName(cmd)));
1482 if (isLeftBraceAhead()) {
1483 skipSpacesOrOneEndl();
1484 m_pendingFormats.insert(m_braceDepth, format);
1488 const auto &arg{getArgument()};
1493 m_indexStartedParagraph =
false;
1502 int outer = m_openedCommands.top();
1505 if (cmd == CMD_COMPARESWITH && m_openedCommands.contains(cmd)) {
1506 location().warning(u"Cannot nest '\\%1' commands"_s.arg(cmdName(cmd)));
1523 m_openedCommands.push(cmd);
1526 QStringLiteral(
"Can't use '\\%1' in '\\%2'").arg(cmdName(cmd), cmdName(outer)));
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
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
1608inline bool DocParser::isAutoLinkString(
const QString &word)
1610 qsizetype start = 0;
1611 return isAutoLinkString(word, start) && (start == word.size());
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635bool DocParser::isAutoLinkString(
const QString &word, qsizetype &curPos)
1637 qsizetype len = word.size();
1638 qsizetype startPos = curPos;
1639 int numUppercase = 0;
1640 int numLowercase = 0;
1641 int numStrangeSymbols = 0;
1643 while (curPos < len) {
1644 unsigned char latin1Ch = word.at(curPos).toLatin1();
1645 if (islower(latin1Ch)) {
1648 }
else if (isupper(latin1Ch)) {
1649 if (curPos > startPos)
1652 }
else if (isdigit(latin1Ch)) {
1653 if (curPos > startPos)
1657 }
else if (latin1Ch ==
'_' || latin1Ch ==
'@') {
1658 ++numStrangeSymbols;
1660 }
else if ((latin1Ch ==
':') && (curPos < len - 1)
1661 && (word.at(curPos + 1) == QLatin1Char(
':'))) {
1662 ++numStrangeSymbols;
1664 }
else if (latin1Ch ==
'(') {
1665 if ((curPos < len - 1) && (word.at(curPos + 1) == QLatin1Char(
')'))) {
1666 ++numStrangeSymbols;
1676 return ((numUppercase >= 1 && numLowercase >= 2) || (numStrangeSymbols > 0 && (numUppercase + numLowercase >= 1)));
1681 if (endCmdFor(m_openedCommands.top()) == endCmd && m_openedCommands.size() > 1) {
1682 m_openedCommands.pop();
1685 bool contains =
false;
1686 QStack<
int> opened2 = m_openedCommands;
1687 while (opened2.size() > 1) {
1696 while (endCmdFor(m_openedCommands.top()) != endCmd && m_openedCommands.size() > 1) {
1698 QStringLiteral(
"Missing '\\%1' before '\\%2'")
1699 .arg(endCmdName(m_openedCommands.top()), cmdName(endCmd)));
1700 m_openedCommands.pop();
1703 location().warning(QStringLiteral(
"Unexpected '\\%1'").arg(cmdName(endCmd)));
1714 m_currentSection =
static_cast<
Doc::
Sections>(unit);
1717 endSection(unit, cmd);
1720 appendAtom(Atom(Atom::SectionLeft, QString::number(unit)));
1723 m_private
->extra->m_tableOfContentsLevels.append(unit);
1724 enterPara(Atom::SectionHeadingLeft, Atom::SectionHeadingRight, QString::number(unit));
1725 m_currentSection = unit;
1731 appendAtom(Atom(Atom::SectionRight, QString::number(m_currentSection)));
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1750 auto line_comment = [
this]() ->
bool {
1752 if (m_position + 2 > m_inputLength)
1754 if (m_input[m_position].unicode() ==
'/') {
1755 if (m_input[m_position + 1].unicode() ==
'/') {
1756 if (m_input[m_position + 2].unicode() ==
'!') {
1764 auto skip_everything_until_newline = [
this]() ->
void {
1765 while (m_position < m_inputLength && m_input[m_position] !=
'\n')
1771 while (m_position < m_inputLength && m_input[m_position] !=
'\n') {
1774 bool skipMe =
false;
1776 if (m_input[m_position] ==
'{') {
1777 target = getArgument();
1779 if (m_position < m_inputLength && m_input[m_position] ==
'{') {
1780 str = getArgument();
1783 if (target.endsWith(
"::"))
1789 target = getArgument();
1790 str = cleanLink(target);
1791 if (target == QLatin1String(
"and") || target == QLatin1String(
"."))
1805 skip_everything_until_newline();
1807 if (m_position < m_inputLength && m_input[m_position] ==
',') {
1810 skip_everything_until_newline();
1811 skipSpacesOrOneEndl();
1812 }
else if (m_position >= m_inputLength || m_input[m_position] !=
'\n') {
1813 location().warning(QStringLiteral(
"Missing comma in '\\%1'").arg(cmdName(
CMD_SA)));
1831 if (ch == QLatin1Char(
' ')) {
1832 if (!atom->string().endsWith(QLatin1Char(
' ')))
1833 atom->appendChar(QLatin1Char(
' '));
1835 atom->appendChar(ch);
1838void DocParser::appendWord(
const QString &word)
1846void DocParser::appendToCode(
const QString &markedCode)
1848 if (!isCode(m_lastAtom)) {
1852 m_lastAtom->concatenateString(markedCode);
1857 if (!isCode(m_lastAtom)) {
1858 appendAtom(
Atom(defaultType, markedCode));
1861 m_lastAtom->concatenateString(markedCode);
1867 if (m_paragraphState != OutsideParagraph)
1876 appendAtom(
Atom(leftType, string));
1877 m_indexStartedParagraph =
false;
1878 m_pendingParagraphLeftType = leftType;
1879 m_pendingParagraphRightType = rightType;
1880 m_pendingParagraphString = string;
1882 m_paragraphState = InSingleLineParagraph;
1884 m_paragraphState = InMultiLineParagraph;
1886 skipSpacesOrOneEndl();
1891 if (m_paragraphState == OutsideParagraph)
1894 if (!m_pendingFormats.isEmpty()) {
1895 location().warning(QStringLiteral(
"Missing '}'"));
1896 m_pendingFormats.clear();
1906 appendAtom(Atom(m_pendingParagraphRightType, m_pendingParagraphString));
1908 m_paragraphState = OutsideParagraph;
1909 m_indexStartedParagraph =
false;
1910 m_pendingParagraphRightType =
Atom::Nop;
1911 m_pendingParagraphString.clear();
1917 if (m_openedLists.isEmpty()) {
1918 m_openedLists.push(OpenedList(OpenedList::Value));
1930 if (!m_openedLists.isEmpty() && (m_openedLists.top().style() == OpenedList::Value)) {
1935 m_openedLists.pop();
1941 if (m_inTableItem) {
1944 m_inTableItem =
false;
1946 if (m_inTableHeader) {
1948 m_inTableHeader =
false;
1952 m_inTableRow =
false;
1956void DocParser::quoteFromFile(
const QString &filename)
1969 auto maybe_resolved_file{(*file_resolver).resolve(filename)};
1970 if (!maybe_resolved_file) {
1979 QString details = std::transform_reduce(
1980 (*file_resolver).get_search_directories().cbegin(),
1981 (*file_resolver).get_search_directories().cend(),
1982 u"Searched directories:"_s,
1984 [](
const DirectoryPath &directory_path) -> QString {
return u' ' + directory_path.value(); }
1987 location().warning(u"Cannot find file to quote from: %1"_s.arg(filename), details);
2003 CodeMarker *marker = CodeMarker::markerForFileName(QString{});
2004 m_quoter.quoteFromFile(filename, QString{}, marker->markedUpCode(QString{},
nullptr, location()));
2005 }
else Doc::quoteFromFile(location(), m_quoter, *maybe_resolved_file);
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033bool DocParser::expandMacro(ArgumentParsingOptions options)
2035 Q_ASSERT(m_input[m_position].unicode() ==
'\\');
2037 if (options == ArgumentParsingOptions::Verbatim)
2041 qsizetype backslashPos = m_position++;
2042 while (m_position < m_input.size() && m_input[m_position].isLetterOrNumber())
2043 cmdStr += m_input[m_position++];
2045 m_endPosition = m_position;
2046 if (!cmdStr.isEmpty()) {
2047 if (s_utilities
.macroHash.contains(cmdStr)) {
2049 if (!macro.m_defaultDef.isEmpty()) {
2050 QString expanded = expandMacroToString(cmdStr, macro);
2051 m_input.replace(backslashPos, m_position - backslashPos, expanded);
2052 m_inputLength = m_input.size();
2053 m_position = backslashPos;
2056 location().warning(
"Macro '%1' does not have a default definition"_L1.arg(cmdStr));
2060 m_position = backslashPos;
2061 if (options != ArgumentParsingOptions::MacroArguments
2063 location().warning(
"Unknown macro '%1'"_L1.arg(cmdStr));
2067 }
else if (m_input[m_position].isSpace()) {
2069 }
else if (m_input[m_position].unicode() ==
'\\') {
2071 m_input.remove(m_position--, 1);
2077void DocParser::expandMacro(
const QString &def,
const QStringList &args)
2079 if (args.isEmpty()) {
2084 for (
int j = 0; j < def.size(); ++j) {
2085 if (
int paramNo = def[j].unicode(); paramNo >= 1 && paramNo <= args.length()) {
2086 if (!rawString.isEmpty()) {
2092 rawString += def[j];
2095 if (!rawString.isEmpty())
2100QString
DocParser::expandMacroToString(
const QString &name,
const Macro ¯o)
2102 const QString &def{macro.m_defaultDef};
2106 rawString = macro.m_defaultDef;
2108 QStringList args{getMacroArguments(name, macro)};
2110 for (
int j = 0; j < def.size(); ++j) {
2111 int paramNo = def[j].unicode();
2112 rawString += (paramNo >= 1 && paramNo <= args.length()) ? args[paramNo - 1] : def[j];
2115 QString matchExpr{macro.m_otherDefs.value(
"match")};
2116 if (matchExpr.isEmpty())
2120 QRegularExpression re(matchExpr);
2121 int capStart = (re.captureCount() > 0) ? 1 : 0;
2123 QRegularExpressionMatch match;
2124 while ((match = re.match(rawString, i)).hasMatch()) {
2125 for (
int c = capStart; c <= re.captureCount(); ++c)
2126 result += match.captured(c);
2127 i = match.capturedEnd();
2135 QString name = getOptionalArgument();
2137 if (name ==
"section1") {
2139 }
else if (name ==
"section2") {
2141 }
else if (name ==
"section3") {
2143 }
else if (name ==
"section4") {
2145 }
else if (name.isEmpty()) {
2148 location().warning(QStringLiteral(
"Invalid section '%1'").arg(name));
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163QString
DocParser::getBracedArgument(ArgumentParsingOptions options)
2167 if (m_position < m_input.size() && m_input[m_position] ==
'{') {
2169 while (m_position < m_input.size() && delimDepth >= 0) {
2170 switch (m_input[m_position].unicode()) {
2173 arg += QLatin1Char(
'{');
2178 if (delimDepth >= 0)
2179 arg += QLatin1Char(
'}');
2183 if (!expandMacro(options))
2184 arg += m_input[m_position++];
2187 if (m_input[m_position].isSpace() && options != ArgumentParsingOptions::Verbatim)
2190 arg += m_input[m_position];
2195 location().warning(QStringLiteral(
"Missing '}'"));
2197 m_endPosition = m_position;
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217QString
DocParser::getArgument(ArgumentParsingOptions options)
2219 skipSpacesOrOneEndl();
2222 qsizetype startPos = m_position;
2223 QString arg = getBracedArgument(options);
2224 if (arg.isEmpty()) {
2225 while ((m_position < m_input.size())
2226 && ((delimDepth > 0) || ((delimDepth == 0) && !m_input[m_position].isSpace()))) {
2227 switch (m_input[m_position].unicode()) {
2232 arg += m_input[m_position];
2239 if (m_position == startPos || delimDepth >= 0) {
2240 arg += m_input[m_position];
2245 if (!expandMacro(options))
2246 arg += m_input[m_position++];
2249 arg += m_input[m_position];
2253 m_endPosition = m_position;
2254 if ((arg.size() > 1) && (QString(
".,:;!?").indexOf(m_input[m_position - 1]) != -1)
2255 && !arg.endsWith(
"...")) {
2256 arg.truncate(arg.size() - 1);
2259 if (arg.size() > 2 && m_input.mid(m_position - 2, 2) ==
"'s") {
2260 arg.truncate(arg.size() - 2);
2264 return arg.simplified();
2268
2269
2270
2271
2272
2273QString
DocParser::getBracketedArgument()
2277 skipSpacesOrOneEndl();
2278 if (m_position < m_input.size() && m_input[m_position] ==
'[') {
2280 while (m_position < m_input.size() && delimDepth >= 0) {
2281 switch (m_input[m_position].unicode()) {
2284 arg += QLatin1Char(
'[');
2289 if (delimDepth >= 0)
2290 arg += QLatin1Char(
']');
2294 arg += m_input[m_position];
2298 arg += m_input[m_position];
2303 location().warning(QStringLiteral(
"Missing ']'"));
2310
2311
2312
2313
2314
2315QStringList
DocParser::getMacroArguments(
const QString &name,
const Macro ¯o)
2319 if (macro
.numParams == 1 || isLeftBraceAhead()) {
2320 args << getArgument(ArgumentParsingOptions::MacroArguments);
2322 location().warning(QStringLiteral(
"Macro '\\%1' invoked with too few"
2323 " arguments (expected %2, got %3)")
2335 skipSpacesOrOneEndl();
2336 if (m_position + 1 < m_input.size() && m_input[m_position] ==
'\\'
2337 && m_input[m_position + 1].isLetterOrNumber()) {
2340 return getArgument();
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2361 auto lineHasTrailingBackslash = [
this](
bool trailingBackslash) ->
bool {
2362 while (m_position < m_inputLength && m_input[m_position] !=
'\n') {
2363 if (m_input[m_position] ==
'\\' && !trailingBackslash) {
2364 trailingBackslash =
true;
2368 trailingBackslash =
false;
2372 return trailingBackslash;
2375 QString rest_of_line;
2377 bool trailing_backslash{
false };
2378 bool return_simplified_string{
false };
2380 for (qsizetype start_position = m_position; m_position < m_inputLength; ++m_position) {
2381 trailing_backslash = lineHasTrailingBackslash(trailing_backslash);
2383 if (!rest_of_line.isEmpty())
2384 rest_of_line += QLatin1Char(
' ');
2385 rest_of_line += m_input.sliced(start_position, m_position - start_position);
2387 if (trailing_backslash) {
2388 rest_of_line.truncate(rest_of_line.lastIndexOf(
'\\'));
2389 return_simplified_string =
true;
2392 if (m_position < m_inputLength)
2395 if (!trailing_backslash)
2397 start_position = m_position;
2400 if (return_simplified_string)
2401 return rest_of_line.simplified();
2403 return rest_of_line.trimmed();
2407
2408
2409
2410
2411QString
DocParser::getMetaCommandArgument(
const QString &cmdStr)
2415 qsizetype begin = m_position;
2418 while (m_position < m_input.size() && (m_input[m_position] !=
'\n' || parenDepth > 0)) {
2419 if (m_input.at(m_position) ==
'(')
2421 else if (m_input.at(m_position) ==
')')
2423 else if (m_input.at(m_position) ==
'\\' && expandMacro(ArgumentParsingOptions::Default))
2427 if (m_position == m_input.size() && parenDepth > 0) {
2429 location().warning(QStringLiteral(
"Unbalanced parentheses in '%1'").arg(cmdStr));
2432 QString t = m_input.mid(begin, m_position - begin).simplified();
2440 QRegularExpression rx(
"\\\\" + cmdName(endCmd) +
"\\b");
2442 auto match = rx.match(m_input, m_position);
2444 if (!match.hasMatch()) {
2445 location().warning(QStringLiteral(
"Missing '\\%1'").arg(cmdName(endCmd)));
2446 m_position = m_input.size();
2448 qsizetype end = match.capturedStart();
2449 t = m_input.mid(m_position, end - m_position);
2450 m_position = match.capturedEnd();
2455void DocParser::expandArgumentsInString(QString &str,
const QStringList &args)
2462 while (j < str.size()) {
2463 if (str[j] ==
'\\' && j < str.size() - 1 && (paramNo = str[j + 1].digitValue()) >= 1
2464 && paramNo <= args.size()) {
2465 const QString &r = args[paramNo - 1];
2466 str.replace(j, 2, r);
2467 j += qMin(1, r.size());
2475
2476
2477
2478
2479
2480
2483 QString code = untabifyEtc(getUntilEnd(cmd));
2484 expandArgumentsInString(code, argStr.split(
" ", Qt::SkipEmptyParts));
2486 int indent = indentLevel(code);
2487 code = dedent(indent, code);
2490 if (!marker && !m_private->m_topics.isEmpty()
2491 && m_private->m_topics[0].m_topic.startsWith(
"qml")) {
2492 auto qmlMarker = CodeMarker::markerForLanguage(
"QML");
2493 marker = (qmlMarker && qmlMarker->recognizeCode(code)) ? qmlMarker :
nullptr;
2495 if (marker ==
nullptr)
2496 marker = CodeMarker::markerForCode(code);
2497 return marker->markedUpCode(code,
nullptr, location());
2502 qsizetype i = m_position;
2504 while (i < m_inputLength && m_input[i].isSpace()) {
2505 if (m_input[i] ==
'\n')
2515 qsizetype i = m_position;
2517 while (i < m_inputLength && m_input[i].isSpace() && numEndl < 2) {
2519 if (m_input[i] ==
'\n')
2523 return numEndl < 2 && i < m_inputLength && m_input[i] ==
'{';
2529 qsizetype i = m_position;
2531 while (i < m_inputLength && m_input[i].isSpace() && numEndl < 2) {
2533 if (m_input[i] ==
'\n')
2537 return numEndl < 2 && i < m_inputLength && m_input[i] ==
'[';
2541
2542
2545 while ((m_position < m_input.size()) && m_input[m_position].isSpace()
2546 && (m_input[m_position].unicode() !=
'\n'))
2551
2552
2555 qsizetype firstEndl = -1;
2556 while (m_position < m_input.size() && m_input[m_position].isSpace()) {
2557 QChar ch = m_input[m_position];
2559 if (firstEndl == -1) {
2560 firstEndl = m_position;
2562 m_position = firstEndl;
2572 while (m_position < m_inputLength && m_input[m_position].isSpace())
2576void DocParser::skipToNextPreprocessorCommand()
2578 QRegularExpression rx(
"\\\\(?:" + cmdName(CMD_IF) + QLatin1Char(
'|') + cmdName(CMD_ELSE)
2579 + QLatin1Char(
'|') + cmdName(CMD_ENDIF) +
")\\b");
2580 auto match = rx.match(m_input, m_position + 1);
2582 if (!match.hasMatch())
2583 m_position = m_input.size();
2585 m_position = match.capturedStart();
2636 return cmds[cmd]
.name;
2641 return cmdName(endCmdFor(cmd));
2647 result.reserve(str.size());
2650 for (
const auto &character : str) {
2651 if (character == QLatin1Char(
'\r'))
2653 if (character == QLatin1Char(
'\t')) {
2654 result += &
" "[column % s_tabSize];
2655 column = ((column / s_tabSize) + 1) * s_tabSize;
2658 if (character == QLatin1Char(
'\n')) {
2659 while (result.endsWith(QLatin1Char(
' ')))
2661 result += character;
2665 result += character;
2669 while (result.endsWith(
"\n\n"))
2670 result.truncate(result.size() - 1);
2671 while (result.startsWith(QLatin1Char(
'\n')))
2672 result = result.mid(1);
2679 int minIndent = INT_MAX;
2682 for (
const auto &character : str) {
2683 if (character ==
'\n') {
2686 if (character !=
' ' && column < minIndent)
2702 for (
const auto &character : str) {
2703 if (character == QLatin1Char(
'\n')) {
2707 if (column >= level)
2708 result += character;
2716
2717
2725
2726
2736
2737
2738
2739
2740
2741
2742
2743
2746 static auto take_while = [](QStringView input,
auto predicate) {
2747 QStringView::size_type end{0};
2749 while (end < input.size() &&
std::invoke(predicate, input[end]))
2752 return std::make_tuple(input.sliced(0, end), input.sliced(end));
2755 static auto peek = [](QStringView input, QChar c) {
2756 return !input.empty() && input.first() == c;
2759 static auto skip_one = [](QStringView input) {
2760 if (input.empty())
return std::make_tuple(QStringView{}, input);
2761 else return std::make_tuple(input.sliced(0, 1), input.sliced(1));
2764 static auto enclosed = [](QStringView input, QChar open, QChar close) {
2765 if (!peek(input, open))
return std::make_tuple(QStringView{}, input);
2767 auto [opened, without_open] = skip_one(input);
2768 auto [parsed, remaining] = take_while(without_open, [close](QChar c){
return c != close; });
2770 if (remaining.empty())
return std::make_tuple(QStringView{}, input);
2772 auto [closed, without_close] = skip_one(remaining);
2774 return std::make_tuple(parsed.trimmed(), without_close);
2777 static auto one_of = [](
auto first,
auto second) {
2778 return [first, second](QStringView input) {
2779 auto [parsed, remaining] =
std::invoke(first, input);
2781 if (parsed.empty())
return std::invoke(second, input);
2782 else return std::make_tuple(parsed, remaining);
2786 static auto collect = [](QStringView input,
auto parser) {
2787 QStringList collected{};
2790 auto [parsed, remaining] =
std::invoke(parser, input);
2792 if (parsed.empty())
break;
2793 collected.append(parsed.toString());
2801 static auto spaces = [](QStringView input) {
2802 return take_while(input, [](QChar c){
return c.isSpace(); });
2805 static auto word = [](QStringView input) {
2806 return take_while(input, [](QChar c){
return !c.isSpace(); });
2809 static auto parse_argument = [](QStringView input) {
2810 auto [_, without_spaces] = spaces(input);
2813 [](QStringView input){
return enclosed(input,
'{',
'}'); },
2818 const QString cmd{DocParser::cmdName(CMD_COMPARESWITH)};
2822 QStringList segments = collect(atom->string(), parse_argument);
2824 QString categoryString;
2825 if (!segments.isEmpty())
2826 categoryString = segments.takeFirst();
2827 auto category = comparisonCategoryFromString(categoryString.toStdString());
2829 if (category == ComparisonCategory::None) {
2830 location.warning(u"Invalid argument to \\%1 command: `%2`"_s.arg(cmd, categoryString),
2831 u"Valid arguments are `strong`, `weak`, `partial`, or `equality`."_s);
2835 if (segments.isEmpty()) {
2836 location.warning(u"Missing argument to \\%1 command."_s.arg(cmd),
2837 u"Provide at least one type name, or a list of types separated by spaces."_s);
2842 segments.removeDuplicates();
2843 atom->setString(segments.join(QLatin1Char(
';')));
2846 priv->m_metaCommandMap[cmd].append(
ArgPair(categoryString, atom->string()));
2847 priv->m_metacommandsUsed.insert(cmd);
2850 const auto end{priv
->extra->m_comparesWithMap.cend()};
2851 priv
->extra->m_comparesWithMap.insert(end, category, text);
#define ATOM_FORMATTING_TELETYPE
#define ATOM_FORMATTING_UNDERLINE
#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()
The Config class contains the configuration variables for controlling how qdoc produces documentation...
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)
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.
#define CONFIG_IGNOREWORDS
#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)
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.