36#include <unordered_map>
57#if !ENABLE_MARKDOWN_TRACING
61#define AUTO_TRACE(...) (void)0
62#define AUTO_TRACE_ADD(...) (void)0
63#define AUTO_TRACE_EXIT(...) (void)0
78 ((c>='a' && c<='z') || \
79 (c>='A' && c<='Z') || \
80 (c>='0' && c<='9') || \
81 (static_cast<unsigned char>(c)>=0x80))
85 (c=='-' || c=='+' || c=='!' || \
86 c=='?' || c=='$' || c=='@' || \
87 c=='&' || c=='*' || c=='%' || \
88 c=='[' || c=='(' || c=='.' || \
89 c=='>' || c==':' || c==',' || \
90 c==';' || c=='\'' || c=='"' || c=='`')
92// is character at position i in data allowed before an emphasis section
93#define isOpenEmphChar(c) \
94 (c=='\n' || c==' ' || c=='\'' || c=='<' || \
95 c=='>' || c=='{' || c=='(' || c=='[' || \
96 c==',' || c==':' || c==';')
98// is character at position i in data an escape that prevents ending an emphasis section
99// so for example *bla (*.txt) is cool*
100#define ignoreCloseEmphChar(c,cn) \
101 (c=='(' || c=='{' || c=='[' || (c=='<' && cn!='/') || \
108 TableCell() : colSpan(false) {}
113struct Markdown::Private
115 Private(const QCString &fn,int line,int indent)
116 : fileName(fn), lineNr(line), indentLevel(indent)
118 // setup callback table for special characters
119 actions[static_cast<unsigned int>('_')] = [this](std::string_view data,size_t offset) { return processEmphasis (data,offset); };
120 actions[static_cast<unsigned int>('*')] = [this](std::string_view data,size_t offset) { return processEmphasis (data,offset); };
121 actions[static_cast<unsigned int>('~')] = [this](std::string_view data,size_t offset) { return processEmphasis (data,offset); };
122 actions[static_cast<unsigned int>('`')] = [this](std::string_view data,size_t offset) { return processCodeSpan (data,offset); };
123 actions[static_cast<unsigned int>('\\')]= [this](std::string_view data,size_t offset) { return processSpecialCommand(data,offset); };
124 actions[static_cast<unsigned int>('@')] = [this](std::string_view data,size_t offset) { return processSpecialCommand(data,offset); };
125 actions[static_cast<unsigned int>('[')] = [this](std::string_view data,size_t offset) { return processLink (data,offset); };
126 actions[static_cast<unsigned int>('!')] = [this](std::string_view data,size_t offset) { return processLink (data,offset); };
127 actions[static_cast<unsigned int>('<')] = [this](std::string_view data,size_t offset) { return processHtmlTag (data,offset); };
128 actions[static_cast<unsigned int>('-')] = [this](std::string_view data,size_t offset) { return processNmdash (data,offset); };
129 actions[static_cast<unsigned int>('"')] = [this](std::string_view data,size_t offset) { return processQuoted (data,offset); };
132 QCString processQuotations(std::string_view data,
size_t refIndent);
133 QCString processBlocks(std::string_view data,
size_t indent);
134 QCString isBlockCommand(std::string_view data,
size_t offset);
135 size_t isSpecialCommand(std::string_view data,
size_t offset);
136 size_t findEndOfLine(std::string_view data,
size_t offset);
137 int processHtmlTagWrite(std::string_view data,
size_t offset,
bool doWrite);
138 int processHtmlTag(std::string_view data,
size_t offset);
139 int processEmphasis(std::string_view data,
size_t offset);
140 int processEmphasis1(std::string_view data,
char c);
141 int processEmphasis2(std::string_view data,
char c);
142 int processEmphasis3(std::string_view data,
char c);
143 int processNmdash(std::string_view data,
size_t offset);
144 int processQuoted(std::string_view data,
size_t offset);
145 int processCodeSpan(std::string_view data,
size_t offset);
146 int processSpecialCommand(std::string_view data,
size_t offset);
147 int processLink(std::string_view data,
size_t offset);
148 size_t findEmphasisChar(std::string_view,
char c,
size_t c_size);
149 void addStrEscapeUtf8Nbsp(std::string_view data);
150 void processInline(std::string_view data);
151 void writeMarkdownImage(std::string_view
fmt,
bool inline_img,
bool explicitTitle,
155 int isHeaderline(std::string_view data,
bool allowAdjustLevel);
156 int isAtxHeader(std::string_view data,
QCString &header,
QCString &
id,
bool allowAdjustLevel,
157 bool *pIsIdGenerated=
nullptr);
158 void writeOneLineHeaderOrRuler(std::string_view data);
159 void writeFencedCodeBlock(std::string_view data, std::string_view lang,
160 size_t blockStart,
size_t blockEnd);
161 size_t writeBlockQuote(std::string_view data);
162 size_t writeCodeBlock(std::string_view,
size_t refIndent);
163 size_t writeTableBlock(std::string_view data);
164 QCString extractTitleId(
QCString &title,
int level,
bool *pIsIdGenerated=
nullptr);
172 using Action_t = std::function<int(std::string_view,
size_t)>;
183 :
prv(std::make_unique<
Private>(fileName,lineNr,indentLevel))
185 using namespace std::placeholders;
210 if (data[0] ==
'\n')
return 1;
212 if (data[0] ==
'\\' &&
qstrncmp(data.data()+1,
"ilinebr ",7)==0)
return data[8]==
' ' ? 9 : 8;
222 const char *p=s.
data();
226 if (c==
'"' && pc!=
'\\') result+=
'\\';
239 bool insideQuote=
FALSE;
241 const char *p=s.
data();
248 if (pc!=
'\\') { insideQuote=!insideQuote; }
258 if ((p[0]==
':') && (p[1]==
':'))
270 case '\\':
if (!insideQuote) { result+=
'\\'; } result+=
'\\';
break;
271 case '@':
if (!insideQuote) { result+=
'\\'; } result+=
'@';
break;
274 case '#':
if (!insideQuote) { result+=
'\\'; } result+=
'#';
break;
275 case '$':
if (!insideQuote) { result+=
'\\'; } result+=
'$';
break;
276 case '&':
if (!insideQuote) { result+=
'\\'; } result+=
'&';
break;
291 if (leftMarker && rightMarker)
299 else if (rightMarker)
314 for (
const auto &attr_ : attrList)
317 int i = attr.
find(
':');
324 return attr.
mid(i+1);
361 using EndBlockFunc = QCString (*)(
const std::string &,bool,char);
363 static const auto getEndBlock = [](
const std::string &blockName,bool,char) -> QCString
365 return "end"+blockName;
367 static const auto getEndCode = [](
const std::string &blockName,
bool openBracket,char) -> QCString
369 return openBracket ? QCString(
"}") :
"end"+blockName;
371 static const auto getEndUml = [](
const std::string &,bool,char) -> QCString
375 static const auto getEndFormula = [](
const std::string &,bool,
char nextChar) -> QCString
379 case '$':
return "f$";
380 case '(':
return "f)";
381 case '[':
return "f]";
382 case '{':
return "f}";
388 static const std::unordered_map<std::string,EndBlockFunc> blockNames =
390 {
"dot", getEndBlock },
391 {
"code", getEndCode },
392 {
"icode", getEndBlock },
393 {
"msc", getEndBlock },
394 {
"verbatim", getEndBlock },
395 {
"iverbatim", getEndBlock },
396 {
"iliteral", getEndBlock },
397 {
"latexonly", getEndBlock },
398 {
"htmlonly", getEndBlock },
399 {
"xmlonly", getEndBlock },
400 {
"rtfonly", getEndBlock },
401 {
"manonly", getEndBlock },
402 {
"docbookonly", getEndBlock },
403 {
"startuml", getEndUml },
404 {
"f", getEndFormula }
407 const size_t size = data.size();
408 bool openBracket = offset>0 && data.data()[-1]==
'{';
409 bool isEscaped = offset>0 && (data.data()[-1]==
'\\' || data.data()[-1]==
'@');
410 if (isEscaped)
return QCString();
413 while (
end<size && (data[
end]>=
'a' && data[
end]<=
'z'))
end++;
414 if (
end==1)
return QCString();
415 std::string blockName(data.substr(1,
end-1));
416 auto it = blockNames.find(blockName);
418 if (it!=blockNames.end())
420 result = it->second(blockName, openBracket,
end<size ? data[
end] : 0);
430 using EndCmdFunc = size_t (*)(std::string_view,size_t);
432 static const auto endOfLine = [](std::string_view data_,
size_t offset_) ->
size_t
437 while (offset_<data_.size() && ((c=data_[offset_])!=
'\n' || lc==
'\\'))
439 if (c==
'\\') lc=
'\\';
440 else if (c!=
' ') lc=0;
446 static const auto endOfLabel = [](std::string_view data_,
size_t offset_) ->
size_t
448 if (offset_<data_.size() && data_[offset_]==
' ')
453 while (offset_<data_.size() && data_[offset_]==
' ') offset_++;
455 while (offset_<data_.size() && (c=data_[offset_])!=
' ' && c!=
'\\' && c!=
'@' && c!=
'\n') offset_++;
461 static const auto endOfLabelOpt = [](std::string_view data_,
size_t offset_) ->
size_t
463 size_t index=offset_;
464 if (index<data_.size() && data_[index]==
' ')
467 while (index<data_.size() && data_[index]==
' ') index++;
469 if (index<data_.size() && data_[index]==
'{')
473 while (index<data_.size() && (c=data_[index])!=
'}' && c!=
'\\' && c!=
'@' && c!=
'\n') index++;
474 if (index==data_.size() || data_[index]!=
'}')
return 0;
477 return endOfLabel(data_,offset_);
480 static const auto endOfParam = [](std::string_view data_,
size_t offset_) ->
size_t
482 size_t index=offset_;
483 if (index<data_.size() && data_[index]==
' ')
486 while (index<data_.size() && data_[index]==
' ') index++;
488 if (index<data_.size() && data_[index]==
'[')
492 while (index<data_.size() && (c=data_[index])!=
']' && c!=
'\n') index++;
493 if (index==data_.size() || data_[index]!=
']')
return 0;
496 return endOfLabel(data_,offset_);
499 static const auto endOfFuncLike = [](std::string_view data_,
size_t offset_,
bool allowSpaces) ->
size_t
501 if (offset_<data_.size() && data_[offset_]==
' ')
506 while (offset_<data_.size() && data_[offset_]==
' ')
511 while (offset_<data_.size() && (c=data_[offset_])!=
'\n' && (allowSpaces || c!=
' ') && c!=
'(')
519 while (offset_<data_.size() && (c=data_[offset_++]))
522 else if (c==
')') count--;
523 if (count==0)
return offset_;
531 static const auto endOfFunc = [](std::string_view data_,
size_t offset_) ->
size_t
533 return endOfFuncLike(data_,offset_,
true);
536 static const auto endOfGuard = [](std::string_view data_,
size_t offset_) ->
size_t
538 return endOfFuncLike(data_,offset_,
false);
541 static const std::unordered_map<std::string,EndCmdFunc> cmdNames =
544 {
"addindex", endOfLine },
545 {
"addtogroup", endOfLabel },
546 {
"anchor", endOfLabel },
549 {
"category", endOfLine },
550 {
"cite", endOfLabel },
551 {
"class", endOfLine },
552 {
"concept", endOfLine },
553 {
"copybrief", endOfFunc },
554 {
"copydetails", endOfFunc },
555 {
"copydoc", endOfFunc },
556 {
"def", endOfFunc },
557 {
"defgroup", endOfLabel },
558 {
"diafile", endOfLine },
559 {
"dir", endOfLine },
560 {
"dockbookinclude",endOfLine },
561 {
"dontinclude", endOfLine },
562 {
"dotfile", endOfLine },
564 {
"elseif", endOfGuard },
565 {
"em", endOfLabel },
566 {
"emoji", endOfLabel },
567 {
"enum", endOfLabel },
568 {
"example", endOfLine },
569 {
"exception", endOfLine },
570 {
"extends", endOfLabel },
571 {
"file", endOfLine },
573 {
"headerfile", endOfLine },
574 {
"htmlinclude", endOfLine },
575 {
"ianchor", endOfLabelOpt },
576 {
"idlexcept", endOfLine },
577 {
"if", endOfGuard },
578 {
"ifnot", endOfGuard },
579 {
"image", endOfLine },
580 {
"implements", endOfLine },
581 {
"include", endOfLine },
582 {
"includedoc", endOfLine },
583 {
"includelineno", endOfLine },
584 {
"ingroup", endOfLabel },
585 {
"interface", endOfLine },
586 {
"latexinclude", endOfLine },
587 {
"maninclude", endOfLine },
588 {
"memberof", endOfLabel },
589 {
"mscfile", endOfLine },
590 {
"namespace", endOfLabel },
591 {
"noop", endOfLine },
592 {
"overload", endOfLine },
594 {
"package", endOfLabel },
595 {
"page", endOfLabel },
596 {
"paragraph", endOfLabel },
597 {
"param", endOfParam },
598 {
"property", endOfLine },
599 {
"protocol", endOfLine },
600 {
"qualifier", endOfLine },
601 {
"ref", endOfLabel },
602 {
"refitem", endOfLine },
603 {
"related", endOfLabel },
604 {
"relatedalso", endOfLabel },
605 {
"relates", endOfLabel },
606 {
"relatesalso", endOfLabel },
607 {
"retval", endOfLabel },
608 {
"rtfinclude", endOfLine },
609 {
"section", endOfLabel },
610 {
"skip", endOfLine },
611 {
"skipline", endOfLine },
612 {
"snippet", endOfLine },
613 {
"snippetdoc", endOfLine },
614 {
"snippetlineno", endOfLine },
615 {
"struct", endOfLine },
616 {
"subpage", endOfLabel },
617 {
"subparagraph", endOfLabel },
618 {
"subsubparagraph",endOfLabel },
619 {
"subsection", endOfLabel },
620 {
"subsubsection", endOfLabel },
621 {
"throw", endOfLabel },
622 {
"throws", endOfLabel },
623 {
"tparam", endOfLabel },
624 {
"typedef", endOfLine },
625 {
"plantumlfile", endOfLine },
626 {
"union", endOfLine },
627 {
"until", endOfLine },
628 {
"var", endOfLine },
629 {
"verbinclude", endOfLine },
630 {
"weakgroup", endOfLabel },
631 {
"xmlinclude", endOfLine },
632 {
"xrefitem", endOfLabel }
635 bool isEscaped = offset>0 && (data.data()[-1]==
'\\' || data.data()[-1]==
'@');
636 if (isEscaped)
return 0;
638 const size_t size = data.size();
640 while (
end<size && (data[
end]>=
'a' && data[
end]<=
'z'))
end++;
641 if (
end==1)
return 0;
642 std::string cmdName(data.substr(1,
end-1));
644 auto it = cmdNames.find(cmdName);
645 if (it!=cmdNames.end())
648 result = it->second(data,
end);
661 const size_t size = data.size();
665 while (i<size && data[i]!=c &&
666 data[i]!=
'\\' && data[i]!=
'@' &&
667 !(data[i]==
'/' && data[i-1]==
'<') &&
687 while (i+len<size && data[i+len]==c)
694 if (len!=c_size || (i+len<size &&
isIdChar(data[i+len])))
700 return static_cast<int>(i);
708 while (i<size && data[i]==
'`') snb++,i++;
712 while (i<size && enb<snb)
714 if (data[i]==
'`') enb++;
715 if (snb==1 && data[i]==
'\'')
break;
719 else if (data[i]==
'@' || data[i]==
'\\')
721 QCString endBlockName = isBlockCommand(data.substr(i),i);
725 size_t l = endBlockName.
length();
728 if ((data[i]==
'\\' || data[i]==
'@') &&
729 data[i-1]!=
'\\' && data[i-1]!=
'@')
739 else if (i+1<size &&
isIdChar(data[i+1]))
748 else if (data[i-1]==
'<' && data[i]==
'/')
752 else if (data[i]==
'\n')
755 while (i<size && data[i]==
' ') i++;
756 if (i>=size || data[i]==
'\n')
774 const size_t size = data.size();
777 if (size>1 && data[0]==c && data[1]==c) { i=1; }
781 size_t len = findEmphasisChar(data.substr(i), c, 1);
782 if (len==0) {
return 0; }
784 if (i>=size) {
return 0; }
786 if (i+1<size && data[i+1]==c)
791 if (data[i]==c && data[i-1]!=
' ' && data[i-1]!=
'\n')
794 processInline(data.substr(0,i));
797 return static_cast<int>(i+1);
808 const size_t size = data.size();
812 size_t len = findEmphasisChar(data.substr(i), c, 2);
818 if (i+1<size && data[i]==c && data[i+1]==c && i && data[i-1]!=
' ' && data[i-1]!=
'\n')
820 if (c ==
'~') out+=
"<strike>";
821 else out+=
"<strong>";
822 processInline(data.substr(0,i));
823 if (c ==
'~') out+=
"</strike>";
824 else out+=
"</strong>";
826 return static_cast<int>(i+2);
840 const size_t size = data.size();
844 size_t len = findEmphasisChar(data.substr(i), c, 3);
852 if (data[i]!=c || data[i-1]==
' ' || data[i-1]==
'\n')
857 if (i+2<size && data[i+1]==c && data[i+2]==c)
860 processInline(data.substr(0,i));
861 out+=
"</strong></em>";
863 return static_cast<int>(i+3);
865 else if (i+1<size && data[i+1]==c)
868 len = processEmphasis1(std::string_view(data.data()-2, size+2), c);
876 return static_cast<int>(len - 2);
882 len = processEmphasis2(std::string_view(data.data()-1, size+1), c);
890 return static_cast<int>(len - 1);
901 const size_t size = data.size();
905 if (i<size && data[i]==
'-')
909 if (i<size && data[i]==
'-')
913 if (i<size && data[i]==
'-')
917 if (count>=2 && offset>=2 &&
qstrncmp(data.data()-2,
"<!",2)==0)
919 if (count==2 && size > 2 && data[2]==
'>')
921 if (count==3 && size > 3 && data[3]==
'>')
923 if (count==2 && (offset<8 ||
qstrncmp(data.data()-8,
"operator",8)!=0))
943 const size_t size = data.size();
946 while (i<size && data[i]!=
'"' && nl<2)
948 if (data[i]==
'\n') nl++;
951 if (i<size && data[i]==
'"' && nl<2)
953 out+=data.substr(0,i+1);
955 return static_cast<int>(i+1);
967 if (offset>0 && data.data()[-1]==
'\\') {
return 0; }
969 const size_t size = data.size();
975 while (i<size &&
isIdChar(data[i])) i++,l++;
976 QCString tagName(data.substr(1,i-1));
977 if (tagName.
lower()==
"pre")
979 bool insideStr=
FALSE;
983 if (!insideStr && c==
'<')
985 if (data[i+1]==
'/' &&
986 tolower(data[i+2])==
'p' && tolower(data[i+3])==
'r' &&
987 tolower(data[i+4])==
'e' && tolower(data[i+5])==
'>')
989 if (doWrite) out+=data.substr(0,i+6);
992 return static_cast<int>(i+6);
995 else if (insideStr && c==
'"')
997 if (data[i-1]!=
'\\') insideStr=
FALSE;
1010 if (data[i]==
'/' && i<size-1 && data[i+1]==
'>')
1013 if (doWrite) out+=data.substr(0,i+2);
1015 return static_cast<int>(i+2);
1017 else if (data[i]==
'>')
1020 if (doWrite) out+=data.substr(0,i+1);
1022 return static_cast<int>(i+1);
1024 else if (data[i]==
' ')
1027 bool insideAttr=
FALSE;
1030 if (!insideAttr && data[i]==
'"')
1034 else if (data[i]==
'"' && data[i-1]!=
'\\')
1038 else if (!insideAttr && data[i]==
'>')
1041 if (doWrite) out+=data.substr(0,i+1);
1043 return static_cast<int>(i+1);
1057 return processHtmlTagWrite(data,offset,
true);
1063 const size_t size = data.size();
1075 if (size>2 && c!=
'~' && data[1]!=c)
1078 if (data[1]==
' ' || data[1]==
'\n' ||
1079 (ret = processEmphasis1(data.substr(1), c)) == 0)
1086 if (size>3 && data[1]==c && data[2]!=c)
1088 if (data[2]==
' ' || data[2]==
'\n' ||
1089 (ret = processEmphasis2(data.substr(2), c)) == 0)
1096 if (size>4 && c!=
'~' && data[1]==c && data[2]==c && data[3]!=c)
1098 if (data[3]==
' ' || data[3]==
'\n' ||
1099 (ret = processEmphasis3(data.substr(3), c)) == 0)
1110 std::string_view fmt,
bool inline_img,
bool explicitTitle,
1111 const QCString &title,
const QCString &content,
1112 const QCString &link,
const QCString &attrs,
1115 AUTO_TRACE(
"fmt={} inline_img={} explicitTitle={} title={} content={} link={} attrs={}",
1126 out+=link.
mid(fd ? 0 : 5);
1127 if (!explicitTitle && !content.
isEmpty())
1133 else if ((content.
isEmpty() || explicitTitle) && !title.
isEmpty())
1155 const size_t size = data.size();
1159 bool isImageLink =
FALSE;
1160 bool isImageInline =
FALSE;
1166 if (size<2 || data[1]!=
'[')
1175 while (pos>=-
static_cast<int>(offset) && numNLsNeeded>0)
1177 if (data.data()[pos]==
'\n') numNLsNeeded--;
1178 else if (data.data()[pos]!=
' ')
1188 size_t contentStart=i;
1195 if (data[i-1]==
'\\')
1198 else if (data[i]==
'[')
1202 else if (data[i]==
']')
1205 if (level<=0)
break;
1207 else if (data[i]==
'\n')
1210 if (nl>1) {
return 0; }
1216 if (i>=size)
return 0;
1217 size_t contentEnd=i;
1218 content = data.substr(contentStart,contentEnd-contentStart);
1220 if (!isImageLink && content.
isEmpty()) {
return 0; }
1223 bool whiteSpace =
false;
1225 while (i<size && data[i]==
' ') { whiteSpace =
true; i++; }
1226 if (i<size && data[i]==
'\n')
1231 while (i<size && data[i]==
' ') i++;
1233 if (whiteSpace && i<size && (data[i]==
'(' || data[i]==
'['))
return 0;
1235 bool explicitTitle=
FALSE;
1236 if (i<size && data[i]==
'(')
1239 while (i<size && data[i]==
' ') i++;
1240 bool uriFormat=
false;
1241 if (i<size && data[i]==
'<') { i++; uriFormat=
true; }
1244 while (i<size && data[i]!=
'\'' && data[i]!=
'"' && braceCount>0)
1249 if (nl>1) {
return 0; }
1251 else if (data[i]==
'(')
1255 else if (data[i]==
')')
1266 if (i>=size || data[i]==
'\n') {
return 0; }
1267 link = data.substr(linkStart,i-linkStart);
1270 if (link.
isEmpty()) {
return 0; }
1274 if (data[i]==
'\'' || data[i]==
'"')
1278 size_t titleStart=i;
1284 if (nl>1) {
return 0; }
1287 else if (data[i]==
'\\')
1291 else if (data[i]==c)
1302 size_t titleEnd = i-1;
1304 while (titleEnd>titleStart && data[titleEnd]==
' ') titleEnd--;
1305 if (data[titleEnd]==c)
1307 title = data.substr(titleStart,titleEnd-titleStart);
1311 if (data[i]==
' ')i++;
1312 else if (data[i] ==
')')
break;
1326 else if (i<size && data[i]==
'[')
1332 while (i<size && data[i]!=
']')
1337 if (nl>1) {
return 0; }
1341 if (i>=size) {
return 0; }
1343 link = data.substr(linkStart,i-linkStart);
1351 QCString link_lower = link.
lower();
1352 auto lr_it=linkRefs.
find(link_lower.
str());
1353 if (lr_it!=linkRefs.end())
1355 link = lr_it->second.link;
1356 title = lr_it->second.title;
1366 else if (i<size && data[i]!=
':' && !content.
isEmpty())
1368 QCString content_lower = content.
lower();
1369 auto lr_it = linkRefs.
find(content_lower.
str());
1371 if (lr_it!=linkRefs.end())
1373 link = lr_it->second.link;
1374 title = lr_it->second.title;
1378 else if (content==
"TOC")
1396 QCString attributes;
1401 while (j<size && data[j]==
' ') { j++; }
1402 if (j<size && data[j]==
'{')
1407 size_t attributesStart=i;
1412 if (data[i-1]==
'\\')
1415 else if (data[i]==
'{')
1419 else if (data[i]==
'}')
1422 if (level<=0)
break;
1424 else if (data[i]==
'\n')
1427 if (nl>1) {
return 0; }
1432 if (i>=size)
return 0;
1433 size_t attributesEnd=i;
1434 attributes = data.substr(attributesStart,attributesEnd-attributesStart);
1443 while (pos<size && numNLsNeeded>0)
1445 if (data[pos]==
'\n') numNLsNeeded--;
1446 else if (data[pos]!=
' ')
1461 out+=
"@tableofcontents{html:";
1462 out+=QCString().
setNum(toc_level);
1466 else if (isImageLink)
1469 FileDef *fd=
nullptr;
1470 if (link.
find(
"@ref ")!=-1 || link.
find(
"\\ref ")!=-1 ||
1475 writeMarkdownImage(
"html", isImageInline, explicitTitle, title, content, link, attributes, fd);
1476 writeMarkdownImage(
"latex", isImageInline, explicitTitle, title, content, link, attributes, fd);
1477 writeMarkdownImage(
"rtf", isImageInline, explicitTitle, title, content, link, attributes, fd);
1478 writeMarkdownImage(
"docbook", isImageInline, explicitTitle, title, content, link, attributes, fd);
1479 writeMarkdownImage(
"xml", isImageInline, explicitTitle, title, content, link, attributes, fd);
1509 FileInfo forg(link.
str());
1516 FileInfo fi(fileName.
str());
1518 FileInfo fmd(mdFile.
str());
1533 if (explicitTitle && !title.
isEmpty())
1539 processInline(std::string_view(
substitute(content,
"\"",
""").str()));
1543 else if ((lp=link.
find(
'#'))!=-1 || link.
find(
'/')!=-1 || link.
find(
'.')!=-1)
1545 if (lp==0 || (lp>0 && !
isURL(link) &&
Config_getEnum(MARKDOWN_ID_STYLE)==MARKDOWN_ID_STYLE_t::GITHUB))
1558 for (
int ii = 0; ii < nlTotal; ii++) out+=
"\n";
1569 processInline(std::string_view(content.
str()));
1580 return static_cast<int>(i);
1587 const size_t size = data.size();
1591 while (nb<size && data[nb]==
'`')
1605 else if (data[
end]==
'\n')
1608 if (pc ==
'\n')
return 0;
1615 out+=data.substr(nb,
end-nb);
1617 return static_cast<int>(
end+1);
1621 if (data[
end]!=
' ') pc = data[
end];
1625 if (i < nb && end >= size)
1631 size_t f_begin = nb;
1632 while (f_begin <
end && data[f_begin]==
' ')
1636 size_t f_end =
end - nb;
1637 while (f_end > nb && data[f_end-1]==
' ')
1645 if (f_begin < f_end)
1647 QCString codeFragment = data.substr(f_begin, f_end-f_begin);
1653 return static_cast<int>(
end);
1672 const size_t size = data.size();
1674 QCString endBlockName = isBlockCommand(data,offset);
1678 size_t l = endBlockName.
length();
1681 if ((data[i]==
'\\' || data[i]==
'@') &&
1682 data[i-1]!=
'\\' && data[i-1]!=
'@')
1687 addStrEscapeUtf8Nbsp(data.substr(0,i+1+l));
1689 return static_cast<int>(i+1+l);
1695 size_t endPos = isSpecialCommand(data,offset);
1698 out+=data.substr(0,endPos);
1699 return static_cast<int>(endPos);
1701 if (size>1 && data[0]==
'\\')
1704 if (c==
'[' || c==
']' || c==
'*' || c==
'!' || c==
'(' || c==
')' || c==
'`' || c==
'_')
1710 else if (c==
'\\' || c==
'@')
1712 out+=data.substr(0,2);
1716 else if (c==
'-' && size>3 && data[2]==
'-' && data[3]==
'-')
1718 out+=data.substr(1,3);
1722 else if (c==
'-' && size>2 && data[2]==
'-')
1724 out+=data.substr(1,2);
1729 else if (size>1 && data[0]==
'@')
1732 if (c==
'\\' || c==
'@')
1734 out+=data.substr(0,2);
1748 const size_t size = data.size();
1752 while (
end<size && ((action=actions[
static_cast<uint8_t
>(data[
end])])==
nullptr))
end++;
1754 out+=data.substr(i,
end-i);
1755 if (
end>=size)
break;
1758 int iend = action(data.substr(i),i);
1776 const size_t size = data.size();
1777 while (i<size && data[i]==
' ') i++;
1778 if (i==size)
return 0;
1783 while (i<size && data[i]==
'=') i++,c++;
1784 while (i<size && data[i]==
' ') i++;
1785 int level = (c>1 && (i>=size || data[i]==
'\n')) ? 1 : 0;
1786 if (allowAdjustLevel && level==1 && indentLevel==-1)
1796 return indentLevel+level;
1801 while (i<size && data[i]==
'-') i++,c++;
1802 while (i<size && data[i]==
' ') i++;
1803 return (c>1 && (i>=size || data[i]==
'\n')) ? indentLevel+2 : 0;
1813 const size_t size = data.size();
1814 while (i<size && data[i]==
' ') i++;
1819 while (i<size && (data[i]==
'>' || data[i]==
' '))
1821 if (data[i]==
'>') level++;
1826 bool res = (level>0 && i<size && ((data[i-1]==
' ') || data[i]==
'\n')) || (level > 1);
1838static size_t isLinkRef(std::string_view data, QCString &refid, QCString &link, QCString &title)
1841 const size_t size = data.size();
1844 while (i<size && data[i]==
' ') i++;
1845 if (i>=size || data[i]!=
'[') {
return 0; }
1847 size_t refIdStart=i;
1848 while (i<size && data[i]!=
'\n' && data[i]!=
']') i++;
1849 if (i>=size || data[i]!=
']') {
return 0; }
1850 refid = data.substr(refIdStart,i-refIdStart);
1851 if (refid.
isEmpty()) {
return 0; }
1855 if (i>=size || data[i]!=
':') {
return 0; }
1859 while (i<size && data[i]==
' ') i++;
1860 if (i<size && data[i]==
'\n')
1863 while (i<size && data[i]==
' ') i++;
1865 if (i>=size) {
return 0; }
1867 if (i<size && data[i]==
'<') i++;
1869 while (i<size && data[i]!=
' ' && data[i]!=
'\n') i++;
1871 if (i<size && data[i]==
'>') i++;
1872 if (linkStart==linkEnd) {
return 0; }
1873 link = data.substr(linkStart,linkEnd-linkStart);
1875 if (link==
"@ref" || link==
"\\ref")
1878 while (i<size && data[i]!=
'\n' && data[i]!=
'"') i++;
1879 link+=data.substr(argStart,i-argStart);
1886 while (i<size && data[i]==
' ') i++;
1887 if (i<size && data[i]==
'\n')
1891 while (i<size && data[i]==
' ') i++;
1895 AUTO_TRACE_EXIT(
"result={}: end of isLinkRef while looking for title",i);
1900 if (c==
'\'' || c==
'"' || c==
'(')
1905 size_t titleStart=i;
1907 while (i<size && data[i]!=
'\n') i++;
1912 while (
end>titleStart && data[
end]!=c)
end--;
1915 title = data.substr(titleStart,
end-titleStart);
1919 while (i<size && data[i]==
' ') i++;
1931 size_t size = data.size();
1932 if (size>0 && data[size-1]==
'\n') size--;
1933 while (i<size && data[i]==
' ') i++;
1934 if (i>=size) {
AUTO_TRACE_EXIT(
"result=false: empty line");
return false; }
1936 if (c!=
'*' && c!=
'-' && c!=
'_')
1948 else if (data[i]!=
' ')
1963 static const reg::Ex r2(R
"({#(\a[\w-]*)}\s*$)");
1965 std::string ti = title.str();
1968 std::string
id = match[1].str();
1969 title = title.
left(match.position());
1972 warn(fileName, lineNr,
"An automatically generated id already has the name '%s'!",
id.c_str());
1978 if ((level>0) && (level<=
Config_getInt(TOC_INCLUDE_HEADINGS)))
1981 if (pIsIdGenerated) *pIsIdGenerated=
true;
1992 QCString &header,QCString &
id,
bool allowAdjustLevel,
bool *pIsIdGenerated)
1996 int level = 0, blanks=0;
1997 const size_t size = data.size();
2000 while (i<size && data[i]==
' ') i++;
2001 if (i>=size || data[i]!=
'#')
2005 while (i<size && data[i]==
'#') i++,level++;
2010 while (i<size && data[i]==
' ') i++,blanks++;
2011 if (level==1 && blanks==0)
2018 while (
end<size && data[
end]!=
'\n')
end++;
2019 while (
end>i && (data[
end-1]==
'#' || data[
end-1]==
' '))
end--;
2022 header = data.substr(i,
end-i);
2023 id = extractTitleId(header, level, pIsIdGenerated);
2026 int idx=
static_cast<int>(header.
length())-1;
2027 while (idx>=0 && (header.
at(idx)==
'#' || header.
at(idx)==
' ')) idx--;
2028 header=header.
left(idx+1);
2031 if (allowAdjustLevel && level==1 && indentLevel==-1)
2053 int res = level+indentLevel;
2062 while (i<data.size())
2073 (data[(i)]=='<' && \
2074 (data[(i)+1]=='l' || data[(i)+1]=='L') && \
2075 (data[(i)+2]=='i' || data[(i)+2]=='I') && \
2084 const size_t size=data.size();
2088 bool listMarkerSkipped=
FALSE;
2091 (!listMarkerSkipped &&
2092 (data[i]==
'+' || data[i]==
'-' || data[i]==
'*' ||
2093 (data[i]==
'#' && i>0 && data[i-1]==
'-') ||
2094 (isDigit=(data[i]>=
'1' && data[i]<=
'9')) ||
2095 (isLi=(size>=3 && i<size-3 &&
isLiTag(i)))
2104 while (j<size && ((data[j]>=
'0' && data[j]<=
'9') || data[j]==
'.'))
2108 if (j<size-1 && data[j+1]==
' ')
2110 listMarkerSkipped=
TRUE;
2127 listMarkerSkipped=
TRUE;
2129 else if (data[i]==
'-' && size>=2 && i<size-2 && data[i+1]==
'#' && data[i+2]==
' ')
2131 listMarkerSkipped=
TRUE;
2135 else if (data[i]!=
' ' && i<size-1 && data[i+1]==
' ')
2137 listMarkerSkipped=
TRUE;
2139 if (data[i]!=
' ' && !listMarkerSkipped)
2152 size_t normalIndent = 0;
2153 while (normalIndent<data.size() && data[normalIndent]==
' ') normalIndent++;
2155 size_t result = listIndent>normalIndent ? listIndent : 0;
2166 while (i<data.size())
2172 else if (data[i]==
'\n')
2176 else if (data[i]!=
' ' && data[i]!=
'\t')
2188 QCString &lang,
size_t &start,
size_t &
end,
size_t &offset)
2191 const char dot =
'.';
2192 auto isAlphaChar = [ ](
char c) {
return (c>=
'A' && c<=
'Z') || (c>=
'a' && c<=
'z'); };
2193 auto isAlphaNChar = [ ](
char c) {
return (c>=
'A' && c<=
'Z') || (c>=
'a' && c<=
'z') || (c>=
'0' && c<=
'9'); };
2194 auto isLangChar = [&](
char c) {
return c==dot || isAlphaChar(c); };
2200 const size_t size = data.size();
2201 while (i<size && data[i]==
' ') indent++,i++;
2202 if (indent>=refIndent+4)
2204 AUTO_TRACE_EXIT(
"result=false: content is part of code block indent={} refIndent={}",indent,refIndent);
2208 if (i<size && data[i]==
'`') tildaChar=
'`';
2209 while (i<size && data[i]==tildaChar) startTildes++,i++;
2212 AUTO_TRACE_EXIT(
"result=false: no fence marker found #tildes={}",startTildes);
2215 if (i<size && data[i]==
'{')
2218 if (data[i] == dot) i++;
2220 while (i<size && (data[i]!=
'\n' && data[i]!=
'}')) i++;
2221 if (i<size && data[i]==
'}')
2223 lang = data.substr(startLang,i-startLang);
2232 else if (i<size && isLangChar(data[i]))
2234 if (data[i] == dot) i++;
2236 if (i<size && isAlphaChar(data[i]))
2239 while (i<size && isAlphaNChar(data[i])) i++;
2241 lang = data.substr(startLang,i-startLang);
2251 if (data[i]==tildaChar)
2255 while (i<size && data[i]==tildaChar) endTildes++,i++;
2256 while (i<size && data[i]==
' ') i++;
2258 if (endTildes==startTildes)
2261 AUTO_TRACE_EXIT(
"result=true: found end marker at offset {} lang='{}'",offset,lang);
2272static bool isCodeBlock(std::string_view data,
size_t offset,
size_t &indent)
2279 const size_t size = data.size();
2280 while (i<size && data[i]==
' ') indent0++,i++;
2284 AUTO_TRACE_EXIT(
"result={}: line is not indented enough {}<4",
false,indent0);
2287 if (indent0>=size || data[indent0]==
'\n')
2289 AUTO_TRACE_EXIT(
"result={}: only spaces at the end of a comment block",
false);
2296 int offset_i =
static_cast<int>(offset);
2300 int j =
static_cast<int>(i)-offset_i-1;
2302 size_t nl_size =
isNewline(std::string_view(data.data()+j,data.size()-j));
2305 nl_pos[nl++]=j+
static_cast<int>(nl_size);
2311 if (i==0 && nl==2) nl_pos[nl++]=-offset_i;
2322 if (!
isEmptyLine(std::string_view(data.data()+nl_pos[1],nl_pos[0]-nl_pos[1]-1)))
2331 std::string_view(data.data()+nl_pos[2],nl_pos[1]-nl_pos[2])));
2337 AUTO_TRACE_EXIT(
"result={}: code block if indent difference >4 spaces",res);
2344 if (nl==1 && !
isEmptyLine(std::string_view(data.data()-offset,offset-1)))
2352 AUTO_TRACE_EXIT(
"result={}: code block if indent difference >4 spaces",res);
2368 const size_t size = data.size();
2371 while (i<size && data[i]==
' ') i++;
2372 if (i<size && data[i]==
'|' && data[i]!=
'\n') i++,n++;
2377 while (i<size && (j =
isNewline(data.substr(i)))==0) i++;
2380 if (j>0 && i>0) i--;
2381 while (i>0 && data[i]==
' ') i--;
2382 if (i>0 && data[i-1]!=
'\\' && data[i]==
'|') i--,n++;
2392 if (data[i]==
'|' && (i==0 || data[i-1]!=
'\\')) columns++;
2393 if (columns==1) columns++;
2397 if (n==2 && columns==0)
2409 size_t cc0=0, start=0,
end=0;
2413 if (i>=data.size() || cc0<1)
2425 if (data[j]!=
':' && data[j]!=
'-' && data[j]!=
'|' && data[j]!=
' ')
2434 AUTO_TRACE_EXIT(
"result=false: different number of columns as previous line {}!={}",cc1,cc0);
2449 const size_t size = data.size();
2451 size_t columns=0, start=0,
end=0;
2453 size_t headerStart = start;
2454 size_t headerEnd =
end;
2460 std::vector<int> columnAlignment(columns);
2462 bool leftMarker=
false, rightMarker=
false, startFound=
false;
2468 if (data[j]==
':') { leftMarker=
TRUE; startFound=
TRUE; }
2469 if (data[j]==
'-') startFound=
TRUE;
2472 if (data[j]==
'-') rightMarker=
FALSE;
2473 else if (data[j]==
':') rightMarker=
TRUE;
2474 if (j<=
end+i && (data[j]==
'|' && (j==0 || data[j-1]!=
'\\')))
2498 std::vector<std::vector<TableCell> > tableContents;
2500 size_t m = headerStart;
2501 std::vector<TableCell> headerContents(columns);
2502 for (k=0;k<columns;k++)
2504 while (m<=headerEnd && (data[m]!=
'|' || (m>0 && data[m-1]==
'\\')))
2506 headerContents[k].cellText += data[m++];
2511 headerContents[k].colSpan = headerContents[k].cellText.isEmpty();
2512 headerContents[k].cellText = headerContents[k].cellText.stripWhiteSpace();
2514 tableContents.push_back(headerContents);
2520 if (cc!=columns)
break;
2524 std::vector<TableCell> rowContents(columns);
2527 if (j<=
end+i && (data[j]==
'|' && (j==0 || data[j-1]!=
'\\')))
2531 rowContents[k].colSpan = rowContents[k].cellText.isEmpty();
2532 rowContents[k].cellText = rowContents[k].cellText.stripWhiteSpace();
2537 rowContents[k].cellText += data[j];
2543 rowContents[k].colSpan = rowContents[k].cellText.isEmpty();
2544 rowContents[k].cellText = rowContents[k].cellText.stripWhiteSpace();
2545 tableContents.push_back(rowContents);
2551 out+=
"<table class=\"markdownTable\">";
2552 QCString cellTag(
"th"), cellClass(
"class=\"markdownTableHead");
2553 for (
size_t row = 0; row < tableContents.size(); row++)
2559 out+=
"\n<tr class=\"markdownTableRowOdd\">";
2563 out+=
"\n<tr class=\"markdownTableRowEven\">";
2568 out+=
"\n <tr class=\"markdownTableHead\">";
2570 for (
size_t c = 0; c < columns; c++)
2573 QCString cellText(tableContents[row][c].cellText);
2579 if (tableContents[row][c].cellText ==
"^")
2583 if (tableContents[row][c].colSpan)
2585 int cr =
static_cast<int>(c);
2586 while ( cr >= 0 && tableContents[row][cr].colSpan)
2590 if (cr >= 0 && tableContents[row][cr].cellText ==
"^")
continue;
2592 size_t rowSpan = 1, spanRow = row+1;
2593 while ((spanRow < tableContents.size()) &&
2594 (tableContents[spanRow][c].cellText ==
"^"))
2600 out+=
" <" + cellTag +
" " + cellClass;
2602 switch (columnAlignment[c])
2614 out+=
" rowspan=\"" + spanStr +
"\"";
2620 while ((c+1 < columns) && tableContents[row][c+1].colSpan)
2629 out+=
" colspan=\"" + spanStr +
"\"";
2633 out+=
"> " + cellText +
" \\ilinebr </" + cellTag +
">";
2636 cellClass =
"class=\"markdownTableBody";
2652 while (i<data.size() && data[i]!=
'\n')
2654 if (data[i]!=
' ' && data[i]!=
'\t') j++;
2657 if (i>=data.size()) {
return 0; }
2658 if (i<2) {
return 0; }
2659 bool res = (j>0 && data[i-1]==
' ' && data[i-2]==
' ');
2675 else if ((level=isAtxHeader(data,header,
id,
TRUE)))
2699 out+=
"</"+hTag+
">\n";
2702 else if (data.size()>0)
2704 size_t tmpSize = data.size();
2705 if (data[data.size()-1] ==
'\n') tmpSize--;
2706 out+=data.substr(0,tmpSize);
2710 out+=
"\\ilinebr<br>";
2712 if (tmpSize != data.size()) out+=
'\n';
2718 {
"[!note]",
"\\note" },
2719 {
"[!warning]",
"\\warning" },
2720 {
"[!tip]",
"\\remark" },
2721 {
"[!caution]",
"\\attention" },
2722 {
"[!important]",
"\\important" }
2731 const size_t size = data.size();
2732 std::string startCmd;
2733 int isGitHubAlert =
false;
2734 int isGitHubFirst =
false;
2739 while (
end<=size && data[
end-1]!=
'\n')
end++;
2744 while (j<
end && (data[j]==
' ' || data[j]==
'>'))
2746 if (data[j]==
'>') { level++; indent=j+1; }
2747 else if (j>0 && data[j-1]==
'>') indent=j+1;
2750 if (indent>0 && j>0 && data[j-1]==
'>' &&
2751 !(j==size || data[j]==
'\n'))
2758 if (level==0 && j<
end-1)
2768 isGitHubAlert =
true;
2769 isGitHubFirst =
true;
2770 startCmd = it->second;
2775 if (level!=1 || !isGitHubAlert)
2777 for (
int l=curLevel;l<level-1;l++)
2779 out+=
"<blockquote>";
2781 out +=
"<blockquote>‍";
2783 else if (!startCmd.empty())
2785 out += startCmd +
" ";
2788 else if (level<curLevel)
2791 if (level==0 && isGitHubAlert)
2797 out +=
"</blockquote>";
2806 if (curLevel!=0 || !isGitHubAlert)
2808 std::string_view txt = data.substr(indent,
end-indent);
2811 if (!isGitHubFirst) out +=
"<br>";
2818 isGitHubFirst =
false;
2833 for (
int l=0;l<curLevel;l++)
2835 out+=
"</blockquote>";
2846 size_t size = data.size();
2847 while (i<data.size() && data[i]==
' ') i++;
2848 if (i<size+8 && data[i]==
'\\' &&
qstrncmp(&data[i+1],
"ifile \"",7)==0)
2850 size_t locStart = i;
2851 if (i>offset) locStart--;
2854 while (i<size-9 && data[i]!=
'\n')
2856 if (data[i]==
'\\' &&
qstrncmp(&data[i+1],
"ilinebr ",8)==0)
2866 location=data.substr(locStart,i-locStart);
2868 while (indent>0 && i<size && data[i]==
' ') i++,indent--;
2869 if (i<size && data[i]==
'\n') i++;
2880 const size_t size = data.size();
2883 out+=
"@iverbatim\n";
2885 std::string location;
2890 while (
end<=size && data[
end-1]!=
'\n')
end++;
2893 while (j<
end && data[j]==
' ') j++,indent++;
2903 while (emptyLines>0)
2911 std::string lineLoc;
2916 out+=data.substr(offset,
end-offset);
2924 out+=
"@endiverbatim";
2925 if (!location.empty())
2933 while (emptyLines>0)
2949 const size_t size = data.size();
2950 size_t nb=0,
end=offset+1, j=0;
2955 if ((data[
end-1]==
'\\' || data[
end-1]==
'@') &&
2956 (
end<=1 || (data[
end-2]!=
'\\' && data[
end-2]!=
'@'))
2959 QCString endBlockName = isBlockCommand(data.substr(
end-1),
end-1);
2963 size_t l = endBlockName.
length();
2964 for (;
end<size-l-1;
end++)
2966 if ((data[
end]==
'\\' || data[
end]==
'@') &&
2967 data[
end-1]!=
'\\' && data[
end-1]!=
'@'
2981 else if (nb==0 && data[
end-1]==
'<' && size>=6 &&
end<size-6 &&
2982 (
end<=1 || (data[
end-2]!=
'\\' && data[
end-2]!=
'@'))
2985 if (tolower(data[
end])==
'p' && tolower(data[
end+1])==
'r' &&
2986 tolower(data[
end+2])==
'e' && (data[
end+3]==
'>' || data[
end+3]==
' '))
2989 end =
end + processHtmlTagWrite(data.substr(
end-1),
end-1,
false);
2997 else if (nb==0 && data[
end-1]==
'`')
2999 while (
end<=size && data[
end-1]==
'`')
end++,nb++;
3001 else if (nb>0 && data[
end-1]==
'`')
3004 while (
end<=size && data[
end-1]==
'`')
end++,enb++;
3018 size_t blockStart,
size_t blockEnd)
3021 if (!lang.empty() && lang[0]==
'.') lang=lang.substr(1);
3022 const size_t size=data.size();
3024 while (i<size && (data[i]==
' ' || data[i]==
'\t'))
3036 addStrEscapeUtf8Nbsp(data.substr(blockStart+i,blockEnd-blockStart));
3045 size_t pi=std::string::npos;
3046 bool newBlock =
false;
3047 bool insideList =
false;
3048 size_t currentIndent = refIndent;
3049 size_t listIndent = refIndent;
3050 const size_t size = data.size();
3054 end = findEndOfLine(data,i);
3057 size_t lineIndent=0;
3058 while (lineIndent<
end && data[i+lineIndent]==
' ') lineIndent++;
3064 if (insideList && lineIndent<currentIndent)
3067 currentIndent = refIndent;
3075 if (listIndent<currentIndent+4)
3079 currentIndent = listIndent;
3086 currentIndent = listIndent;
3095 if (pi!=std::string::npos)
3097 size_t blockStart=0, blockEnd=0, blockOffset=0;
3098 if (
isFencedCodeBlock(data.substr(pi),currentIndent,lang,blockStart,blockEnd,blockOffset))
3100 auto addSpecialCommand = [&](
const QCString &startCmd,
const QCString &endCmd)
3102 size_t cmdPos = pi+blockStart+1;
3103 QCString pl = data.substr(cmdPos,blockEnd-blockStart-1);
3109 if (pl[ii]==
'\n') nl++;
3112 bool addNewLines =
false;
3114 (pl[ii]!=
'\\' && pl[ii]!=
'@') ||
3123 pl =
"@"+startCmd+
"\n" + pl +
"@"+endCmd;
3124 addNewLines =
false;
3139 if (addNewLines)
for (
int j=0;j<nl;j++) out+=
'\n';
3140 processSpecialCommand(pl.
view().substr(ii),ii);
3141 if (addNewLines) out+=
'\n';
3146 addSpecialCommand(
"startuml",
"enduml");
3150 addSpecialCommand(
"dot",
"enddot");
3152 else if (lang==
"msc")
3154 addSpecialCommand(
"msc",
"endmsc");
3158 writeFencedCodeBlock(data.substr(pi),lang.
view(),blockStart,blockEnd);
3161 pi=std::string::npos;
3165 else if (
isBlockQuote(data.substr(pi,i-pi),currentIndent))
3167 i = pi+writeBlockQuote(data.substr(pi));
3168 pi=std::string::npos;
3175 out+=data.substr(pi,i-pi);
3181 if (pi!=std::string::npos && pi<size)
3185 writeBlockQuote(data.substr(pi));
3189 out+=data.substr(pi);
3203 size_t pi = std::string::npos;
3204 QCString id,link,title;
3213 i=writeCodeBlock(out,data,size,blockIndent);
3219 size_t currentIndent = indent;
3220 size_t listIndent = indent;
3221 bool insideList =
false;
3222 bool newBlock =
false;
3225 while (i<data.size())
3227 size_t end = findEndOfLine(data,i);
3230 size_t lineIndent=0;
3232 while (lineIndent<
end && data[i+lineIndent]==
' ') lineIndent++;
3238 if (insideList && lineIndent<currentIndent)
3241 currentIndent = indent;
3249 if (listIndent<currentIndent+4)
3253 currentIndent = listIndent;
3260 currentIndent = listIndent;
3272 if (pi!=std::string::npos)
3274 size_t blockStart=0, blockEnd=0, blockOffset=0;
3276 size_t blockIndent = currentIndent;
3279 QCString endBlockName;
3280 if (data[i]==
'@' || data[i]==
'\\') endBlockName = isBlockCommand(data.substr(i),i);
3284 if (
isLinkRef(data.substr(pi,i-pi),
id,link,title))
3286 linkRefs.emplace(
id.lower().str(),LinkRef(link,title));
3290 writeOneLineHeaderOrRuler(data.substr(pi,i-pi));
3294 size_t l = endBlockName.
length();
3295 while (i+l<data.size())
3297 if ((data[i]==
'\\' || data[i]==
'@') &&
3298 data[i-1]!=
'\\' && data[i-1]!=
'@')
3312 else if ((level=isHeaderline(data.substr(i),
TRUE))>0)
3315 while (pi<data.size() && data[pi]==
' ') pi++;
3316 QCString header = data.substr(pi,i-pi-1);
3317 id = extractTitleId(header, level);
3323 out+=level==1?
"@section ":
"@subsection ";
3331 out+=level==1?
"<h1>":
"<h2>";
3333 out+=level==1?
"\n</h1>\n":
"\n</h2>\n";
3340 pi=std::string::npos;
3345 else if ((ref=
isLinkRef(data.substr(pi),
id,link,title)))
3349 linkRefs.emplace(
id.lower().str(),LinkRef(link,title));
3353 else if (
isFencedCodeBlock(data.substr(pi),currentIndent,lang,blockStart,blockEnd,blockOffset))
3357 writeFencedCodeBlock(data.substr(pi),lang.
view(),blockStart,blockEnd);
3359 pi=std::string::npos;
3366 i+=writeCodeBlock(data.substr(i),blockIndent);
3367 pi=std::string::npos;
3373 i=pi+writeTableBlock(data.substr(pi));
3374 pi=std::string::npos;
3380 writeOneLineHeaderOrRuler(data.substr(pi,i-pi));
3387 if (pi!=std::string::npos && pi<data.size())
3389 if (
isLinkRef(data.substr(pi),
id,link,title))
3393 linkRefs.emplace(
id.lower().str(),LinkRef(link,title));
3397 writeOneLineHeaderOrRuler(data.substr(pi));
3409 std::string_view data(docs.
str());
3410 const size_t size = data.size();
3413 while (i<size && (data[i]==
' ' || data[i]==
'\n'))
3417 if (i<size-5 && data[i]==
'<' &&
qstrncmp(&data[i],
"<!--!",5)==0)
3420 while (i<size && (data[i]==
' ' || data[i]==
'\n'))
3426 (data[i]==
'\\' || data[i]==
'@') &&
3427 (
qstrncmp(&data[i+1],
"page ",5)==0 ||
qstrncmp(&data[i+1],
"mainpage",8)==0)
3430 if (
qstrncmp(&data[i+1],
"page ",5)==0)
3441 else if (i<size-1 &&
3442 (data[i]==
'\\' || data[i]==
'@') &&
3461 QCString docs_org(docs);
3462 std::string_view data(docs_org.
str());
3463 const size_t size = data.size();
3465 while (i<size && (data[i]==
' ' || data[i]==
'\n'))
3467 if (data[i]==
'\n') prepend++;
3470 if (i>=size) {
return QCString(); }
3472 while (end1<size && data[end1-1]!=
'\n') end1++;
3479 while (end2<size && data[end2-1]!=
'\n') end2++;
3480 if (
prv->isHeaderline(data.substr(end1),
FALSE))
3482 title = data.substr(i,end1-i-1);
3483 docs+=
"\n\n"+docs_org.
mid(end2);
3484 id =
prv->extractTitleId(title, 0, &isIdGenerated);
3490 if (i<end1 && prv->isAtxHeader(data.substr(i,end1-i),title,
id,
FALSE,&isIdGenerated)>0)
3493 docs+=docs_org.
mid(end1);
3498 id =
prv->extractTitleId(title, 0, &isIdGenerated);
3509 if (input.
isEmpty())
return input;
3514 if (s.
at(s.
length()-1)!=
'\n') s +=
"\n";
3515 s =
detab(s,refIndent);
3519 s =
prv->processQuotations(s.
view(),refIndent);
3523 s =
prv->processBlocks(s.
view(),refIndent);
3541 const char *p = result.
data();
3544 while (*p==
' ') p++;
3545 while (*p==
'\n') {startNewlines++;p++;};
3548 if (p>result.
data())
3551 result = result.
mid(
static_cast<int>(p-result.
data()));
3561 std::string absFileName = FileInfo(fileName.
str()).absFilePath();
3564 if (i!=-1) baseFn = baseFn.
left(i);
3567 QCString res =
"md_"+baseName;
3574struct MarkdownOutlineParser::Private
3576 CommentScanner commentScanner;
3588 const char *fileBuf,
3589 const std::shared_ptr<Entry> &root,
3592 std::shared_ptr<Entry> current = std::make_shared<Entry>();
3595 current->fileName = fileName;
3596 current->docFile = fileName;
3597 current->docLine = 1;
3598 QCString docs = fileBuf;
3602 bool isIdGenerated =
false;
3604 QCString generatedId;
3610 int indentLevel=title.
isEmpty() ? 0 : -1;
3612 FileInfo fi(fileName.
str());
3617 bool wasEmpty =
id.isEmpty();
3618 if (wasEmpty)
id = mdFileNameId;
3624 if (!mdfileAsMainPage.
isEmpty() &&
3625 (fi.
absFilePath()==FileInfo(mdfileAsMainPage.
str()).absFilePath())
3628 docs.
prepend(
"@ianchor{" + title +
"} " +
id +
"\\ilinebr ");
3629 docs.
prepend(
"@mainpage "+title+
"\\ilinebr ");
3631 else if (
id==
"mainpage" ||
id==
"index")
3633 if (title.
isEmpty()) title = titleFn;
3634 docs.
prepend(
"@ianchor{" + title +
"} " +
id +
"\\ilinebr ");
3635 docs.
prepend(
"@mainpage "+title+
"\\ilinebr ");
3637 else if (isSubdirDocs)
3639 docs.
prepend(
"@dir\\ilinebr ");
3650 docs.
prepend(
"@ianchor{" + title +
"} " +
id +
"\\ilinebr @ianchor{" + relFileName +
"} " + mdFileNameId +
"\\ilinebr ");
3652 else if (!generatedId.
isEmpty())
3654 docs.
prepend(
"@ianchor " + generatedId +
"\\ilinebr ");
3656 else if (
Config_getEnum(MARKDOWN_ID_STYLE)==MARKDOWN_ID_STYLE_t::GITHUB)
3659 docs.
prepend(
"@ianchor{" + title +
"} " + autoId +
"\\ilinebr ");
3661 docs.
prepend(
"@page "+
id+
" "+title+
"\\ilinebr ");
3663 for (
int i = 0; i < prepend; i++) docs.
prepend(
"\n");
3668 static const reg::Ex re(R
"([ ]*[\\@]page\s+(\a[\w-]*)(\s*[^\n]*)\n)");
3670 std::string s = docs.str();
3673 QCString orgLabel = match[1].str();
3674 QCString orgTitle = match[2].str();
3677 docs = docs.
left(match[1].position())+
3680 "\\ilinebr @ianchor{" + orgTitle +
"} "+orgLabel+
"\n"+
3692 p->commentScanner.enterFile(fileName,lineNr);
3694 bool needsEntry =
false;
3697 QCString processedDocs = markdown.
process(docs,lineNr,
true);
3698 while (
p->commentScanner.parseCommentBlock(
3716 QCString docFile = current->docFile;
3717 root->moveToSubEntryAndRefresh(current);
3719 current->docFile = docFile;
3720 current->docLine = lineNr;
3725 root->moveToSubEntryAndKeep(current);
3727 p->commentScanner.leaveFile(fileName,lineNr);
#define eol
The end of line string for this machine.
static AnchorGenerator & instance()
Returns the singleton instance.
static std::string addPrefixIfNeeded(const std::string &anchor)
std::string generate(const std::string &title)
generates an anchor for a section with title.
Clang parser object for a single translation unit, which consists of a source file and the directly o...
static void print(DebugMask mask, int prio, const char *fmt,...)
static ParserManager * parserManager
static FileNameLinkedMap * imageNameLinkedMap
A model of a file symbol.
std::string fileName() const
std::string absFilePath() const
std::unique_ptr< Private > prv
void setIndentLevel(int level)
QCString extractPageTitle(QCString &docs, QCString &id, int &prepend, bool &isIdGenerated)
Markdown(const QCString &fileName, int lineNr, int indentLevel=0)
QCString process(const QCString &input, int &startNewlines, bool fromParseInput=false)
void parseInput(const QCString &fileName, const char *fileBuf, const std::shared_ptr< Entry > &root, ClangTUParser *clangParser) override
Parses a single input file with the goal to build an Entry tree.
~MarkdownOutlineParser() override
void parsePrototype(const QCString &text) override
Callback function called by the comment block scanner.
std::unique_ptr< Private > p
std::unique_ptr< OutlineParserInterface > getOutlineParser(const QCString &extension)
Gets the interface to the parser associated with a given extension.
This is an alternative implementation of QCString.
int find(char c, int index=0, bool cs=TRUE) const
QCString & prepend(const char *s)
size_t length() const
Returns the length of the string, not counting the 0-terminator.
QCString mid(size_t index, size_t len=static_cast< size_t >(-1)) const
bool endsWith(const char *s) const
char & at(size_t i)
Returns a reference to the character at index i.
bool isEmpty() const
Returns TRUE iff the string is empty.
QCString stripWhiteSpace() const
returns a copy of this string with leading and trailing whitespace removed
const std::string & str() const
QCString & setNum(short n)
QCString simplifyWhiteSpace() const
return a copy of this string with leading and trailing whitespace removed and multiple whitespace cha...
QCString right(size_t len) const
QCString & sprintf(const char *format,...)
int findRev(char c, int index=-1, bool cs=TRUE) const
const char * data() const
Returns a pointer to the contents of the string in the form of a 0-terminated C string.
std::string_view view() const
QCString left(size_t len) const
static constexpr int Section
static constexpr int MaxLevel
static constexpr int Subsection
static constexpr int Subsubsection
static constexpr int MinLevel
static constexpr int Paragraph
static constexpr int Subsubparagraph
static constexpr int Subparagraph
#define Config_getInt(name)
#define Config_getBool(name)
#define Config_getString(name)
#define Config_getEnum(name)
std::vector< std::string > StringVector
DirIterator end(const DirIterator &) noexcept
#define AUTO_TRACE_ADD(...)
#define AUTO_TRACE_EXIT(...)
static bool hasLineBreak(std::string_view data)
@ explicitDirPage
docs start with a dir command
@ explicitMainPage
docs start with a mainpage command
@ explicitPage
docs start with a page command
@ notExplicit
docs doesn't start with either page or mainpage
static bool isBlockQuote(std::string_view data, size_t indent)
returns true if this line starts a block quote
static size_t isLinkRef(std::string_view data, QCString &refid, QCString &link, QCString &title)
returns end of the link ref if this is indeed a link reference.
static QCString escapeDoubleQuotes(const QCString &s)
static bool isEndOfList(std::string_view data)
static size_t computeIndentExcludingListMarkers(std::string_view data)
static Alignment markersToAlignment(bool leftMarker, bool rightMarker)
helper function to convert presence of left and/or right alignment markers to a alignment value
static QCString escapeSpecialChars(const QCString &s)
static bool isCodeBlock(std::string_view data, size_t offset, size_t &indent)
static bool isEmptyLine(std::string_view data)
#define AUTO_TRACE_EXIT(...)
static size_t findTableColumns(std::string_view data, size_t &start, size_t &end, size_t &columns)
Finds the location of the table's contains in the string data.
const size_t codeBlockIndent
static ExplicitPageResult isExplicitPage(const QCString &docs)
#define ignoreCloseEmphChar(c, cn)
static const std::unordered_map< std::string, std::string > g_quotationHeaderMap
#define isOpenEmphChar(c)
static bool isFencedCodeBlock(std::string_view data, size_t refIndent, QCString &lang, size_t &start, size_t &end, size_t &offset)
static size_t isListMarker(std::string_view data)
static bool isHRuler(std::string_view data)
static QCString getFilteredImageAttributes(std::string_view fmt, const QCString &attrs)
parse the image attributes and return attributes for given format
bool skipOverFileAndLineCommands(std::string_view data, size_t indent, size_t &offset, std::string &location)
static bool isTableBlock(std::string_view data)
Returns TRUE iff data points to the start of a table block.
size_t isNewline(std::string_view data)
QCString markdownFileNameToId(const QCString &fileName)
processes string s and converts markdown into doxygen/html commands.
#define warn(file, line, fmt,...)
bool isAbsolutePath(const QCString &fileName)
const char * strnstr(const char *haystack, const char *needle, size_t haystack_len)
QCString trunc(const QCString &s, size_t numChars=15)
bool search(std::string_view str, Match &match, const Ex &re, size_t pos)
Search in a given string str starting at position pos for a match against regular expression re.
Portable versions of functions that are platform dependent.
static void decrLevel(yyscan_t yyscanner)
QCString substitute(const QCString &s, const QCString &src, const QCString &dst)
substitute all occurrences of src in s by dst
int qstrncmp(const char *str1, const char *str2, size_t len)
const char * qPrint(const char *s)
Some helper functions for std::string.
std::string_view stripWhiteSpace(std::string_view s)
Given a string view s, returns a new, narrower view on that string, skipping over any leading or trai...
LinkRef(const QCString &l, const QCString &t)
int processEmphasis1(std::string_view data, char c)
process single emphasis
int processQuoted(std::string_view data, size_t offset)
Process quoted section "...", can contain one embedded newline.
void writeMarkdownImage(std::string_view fmt, bool inline_img, bool explicitTitle, const QCString &title, const QCString &content, const QCString &link, const QCString &attributes, const FileDef *fd)
size_t writeTableBlock(std::string_view data)
size_t writeBlockQuote(std::string_view data)
size_t isSpecialCommand(std::string_view data, size_t offset)
std::function< int(std::string_view, size_t)> Action_t
int processEmphasis3(std::string_view data, char c)
Parsing triple emphasis.
int processCodeSpan(std::string_view data, size_t offset)
` parsing a code span (assuming codespan != 0)
int processSpecialCommand(std::string_view data, size_t offset)
QCString extractTitleId(QCString &title, int level, bool *pIsIdGenerated=nullptr)
void writeFencedCodeBlock(std::string_view data, std::string_view lang, size_t blockStart, size_t blockEnd)
int isHeaderline(std::string_view data, bool allowAdjustLevel)
returns whether the line is a setext-style hdr underline
size_t findEmphasisChar(std::string_view, char c, size_t c_size)
looks for the next emph char, skipping other constructs, and stopping when either it is found,...
std::unordered_map< std::string, LinkRef > linkRefs
void addStrEscapeUtf8Nbsp(std::string_view data)
QCString isBlockCommand(std::string_view data, size_t offset)
size_t writeCodeBlock(std::string_view, size_t refIndent)
int processHtmlTag(std::string_view data, size_t offset)
QCString processQuotations(std::string_view data, size_t refIndent)
QCString processBlocks(std::string_view data, size_t indent)
int processEmphasis(std::string_view data, size_t offset)
int processLink(std::string_view data, size_t offset)
int processHtmlTagWrite(std::string_view data, size_t offset, bool doWrite)
Process a HTML tag.
int isAtxHeader(std::string_view data, QCString &header, QCString &id, bool allowAdjustLevel, bool *pIsIdGenerated=nullptr)
size_t findEndOfLine(std::string_view data, size_t offset)
int processEmphasis2(std::string_view data, char c)
process double emphasis
void processInline(std::string_view data)
int processNmdash(std::string_view data, size_t offset)
Process ndash and mdashes.
void writeOneLineHeaderOrRuler(std::string_view data)
std::array< Action_t, 256 > actions
Protection
Protection level of members.
SrcLangExt
Language as given by extension.
SrcLangExt getLanguageFromFileName(const QCString &fileName, SrcLangExt defLang)
QCString escapeCharsInString(const QCString &name, bool allowDots, bool allowUnderscore)
QCString stripExtensionGeneral(const QCString &fName, const QCString &ext)
bool isURL(const QCString &url)
Checks whether the given url starts with a supported protocol.
static QCString stripFromPath(const QCString &p, const StringVector &l)
QCString detab(const QCString &s, size_t &refIndent)
StringVector split(const std::string &s, const std::string &delimiter)
split input string s by string delimiter delimiter.
QCString externalLinkTarget(const bool parent)
QCString getFileNameExtension(const QCString &fn)
FileDef * findFileDef(const FileNameLinkedMap *fnMap, const QCString &n, bool &ambig)
A bunch of utility functions.