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=='_' || 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;
214 return (data.size()>8 && data[8]==
' ') ? 9 : 8;
225 const char *p=s.
data();
229 if (c==
'"' && pc!=
'\\') result+=
'\\';
242 bool insideQuote=
FALSE;
244 const char *p=s.
data();
259 insideQuote=!insideQuote;
271 if ((p[0]==
':') && (p[1]==
':'))
283 case '\\':
if (!insideQuote) { result+=
'\\'; } result+=
'\\';
break;
284 case '@':
if (!insideQuote) { result+=
'\\'; } result+=
'@';
break;
287 case '#':
if (!insideQuote) { result+=
'\\'; } result+=
'#';
break;
288 case '$':
if (!insideQuote) { result+=
'\\'; } result+=
'$';
break;
289 case '&':
if (!insideQuote) { result+=
'\\'; } result+=
'&';
break;
304 if (leftMarker && rightMarker)
312 else if (rightMarker)
327 for (
const auto &attr_ : attrList)
330 int i = attr.
find(
':');
337 return attr.
mid(i+1);
374 using EndBlockFunc =
QCString (*)(
const std::string &,bool,char);
376 static const auto getEndBlock = [](
const std::string &blockName,bool,char) ->
QCString
378 return "end"+blockName;
380 static const auto getEndCode = [](
const std::string &blockName,
bool openBracket,char) ->
QCString
382 return openBracket ?
QCString(
"}") :
"end"+blockName;
384 static const auto getEndUml = [](
const std::string &,bool,char) ->
QCString
388 static const auto getEndFormula = [](
const std::string &,bool,
char nextChar) ->
QCString
392 case '$':
return "f$";
393 case '(':
return "f)";
394 case '[':
return "f]";
395 case '{':
return "f}";
401 static const std::unordered_map<std::string,EndBlockFunc> blockNames =
403 {
"dot", getEndBlock },
404 {
"code", getEndCode },
405 {
"icode", getEndBlock },
406 {
"msc", getEndBlock },
407 {
"verbatim", getEndBlock },
408 {
"iverbatim", getEndBlock },
409 {
"iliteral", getEndBlock },
410 {
"latexonly", getEndBlock },
411 {
"htmlonly", getEndBlock },
412 {
"xmlonly", getEndBlock },
413 {
"rtfonly", getEndBlock },
414 {
"manonly", getEndBlock },
415 {
"docbookonly", getEndBlock },
416 {
"startuml", getEndUml },
417 {
"f", getEndFormula }
420 const size_t size = data.
size();
421 bool openBracket = offset>0 && data.data()[-1]==
'{';
422 bool isEscaped = offset>0 && (data.data()[-1]==
'\\' || data.data()[-1]==
'@');
426 while (
end<size && (data[
end]>=
'a' && data[
end]<=
'z'))
end++;
428 std::string blockName(data.substr(1,
end-1));
429 auto it = blockNames.find(blockName);
431 if (it!=blockNames.end())
433 result = it->second(blockName, openBracket,
end<size ? data[
end] : 0);
443 using EndCmdFunc = size_t (*)(std::string_view,size_t);
445 static const auto endOfLine = [](std::string_view data_,
size_t offset_) ->
size_t
450 while (offset_<data_.size() && ((c=data_[offset_])!=
'\n' || lc==
'\\'))
452 if (c==
'\\') lc=
'\\';
453 else if (c!=
' ') lc=0;
459 static const auto endOfLabels = [](std::string_view data_,
size_t offset_,
bool multi_) ->
size_t
461 if (offset_<data_.size() && data_[offset_]==
' ')
469 while (offset_<data_.size() && data_[offset_]==
' ')
474 while (offset_<data_.size() && (c=data_[offset_])!=
' ' && c!=
',' && c!=
'\\' && c!=
'@' && c!=
'\n')
479 if (multi_ && offset_<data_.size() && (data_[offset_]==
',' || data_[offset_]==
' '))
481 size_t off = offset_;
482 while (off<data_.size() && data_[off]==
' ')
486 if (off<data_.size() && data_[off]==
',')
505 static const auto endOfLabel = [](std::string_view data_,
size_t offset_) ->
size_t
507 return endOfLabels(data_,offset_,
false);
510 static const auto endOfLabelOpt = [](std::string_view data_,
size_t offset_) ->
size_t
512 size_t index=offset_;
513 if (index<data_.size() && data_[index]==
' ')
516 while (index<data_.size() && data_[index]==
' ') index++;
518 if (index<data_.size() && data_[index]==
'{')
522 while (index<data_.size() && (c=data_[index])!=
'}' && c!=
'\\' && c!=
'@' && c!=
'\n') index++;
523 if (index==data_.size() || data_[index]!=
'}')
return 0;
526 return endOfLabel(data_,offset_);
529 static const auto endOfParam = [](std::string_view data_,
size_t offset_) ->
size_t
531 size_t index=offset_;
532 if (index<data_.size() && data_[index]==
' ')
535 while (index<data_.size() && data_[index]==
' ') index++;
537 if (index<data_.size() && data_[index]==
'[')
541 while (index<data_.size() && (c=data_[index])!=
']' && c!=
'\n') index++;
542 if (index==data_.size() || data_[index]!=
']')
return 0;
545 return endOfLabels(data_,offset_,
true);
548 static const auto endOfRetVal = [](std::string_view data_,
size_t offset_) ->
size_t
550 return endOfLabels(data_,offset_,
true);
553 static const auto endOfFuncLike = [](std::string_view data_,
size_t offset_,
bool allowSpaces) ->
size_t
555 if (offset_<data_.size() && data_[offset_]==
' ')
560 while (offset_<data_.size() && data_[offset_]==
' ')
565 while (offset_<data_.size() && (c=data_[offset_])!=
'\n' && (allowSpaces || c!=
' ') && c!=
'(')
567 if (
literal_at(data_.substr(offset_),
"\\ilinebr "))
break;
574 while (offset_<data_.size() && (c=data_[offset_++]))
577 else if (c==
')') count--;
578 if (count==0)
return offset_;
586 static const auto endOfFunc = [](std::string_view data_,
size_t offset_) ->
size_t
588 return endOfFuncLike(data_,offset_,
true);
591 static const auto endOfGuard = [](std::string_view data_,
size_t offset_) ->
size_t
593 return endOfFuncLike(data_,offset_,
false);
596 static const std::unordered_map<std::string,EndCmdFunc> cmdNames =
599 {
"addindex", endOfLine },
600 {
"addtogroup", endOfLabel },
601 {
"anchor", endOfLabel },
604 {
"category", endOfLine },
605 {
"cite", endOfLabel },
606 {
"class", endOfLine },
607 {
"concept", endOfLine },
608 {
"copybrief", endOfFunc },
609 {
"copydetails", endOfFunc },
610 {
"copydoc", endOfFunc },
611 {
"def", endOfFunc },
612 {
"defgroup", endOfLabel },
613 {
"diafile", endOfLine },
614 {
"dir", endOfLine },
615 {
"dockbookinclude",endOfLine },
616 {
"dontinclude", endOfLine },
617 {
"dotfile", endOfLine },
619 {
"elseif", endOfGuard },
620 {
"em", endOfLabel },
621 {
"emoji", endOfLabel },
622 {
"enum", endOfLabel },
623 {
"example", endOfLine },
624 {
"exception", endOfLine },
625 {
"extends", endOfLabel },
626 {
"file", endOfLine },
628 {
"headerfile", endOfLine },
629 {
"htmlinclude", endOfLine },
630 {
"ianchor", endOfLabelOpt },
631 {
"idlexcept", endOfLine },
632 {
"if", endOfGuard },
633 {
"ifnot", endOfGuard },
634 {
"image", endOfLine },
635 {
"implements", endOfLine },
636 {
"include", endOfLine },
637 {
"includedoc", endOfLine },
638 {
"includelineno", endOfLine },
639 {
"ingroup", endOfLabel },
640 {
"interface", endOfLine },
641 {
"latexinclude", endOfLine },
642 {
"maninclude", endOfLine },
643 {
"memberof", endOfLabel },
644 {
"mscfile", endOfLine },
645 {
"namespace", endOfLabel },
646 {
"noop", endOfLine },
647 {
"overload", endOfLine },
649 {
"package", endOfLabel },
650 {
"page", endOfLabel },
651 {
"paragraph", endOfLabel },
652 {
"param", endOfParam },
653 {
"property", endOfLine },
654 {
"protocol", endOfLine },
655 {
"qualifier", endOfLine },
656 {
"ref", endOfLabel },
657 {
"refitem", endOfLine },
658 {
"related", endOfLabel },
659 {
"relatedalso", endOfLabel },
660 {
"relates", endOfLabel },
661 {
"relatesalso", endOfLabel },
662 {
"retval", endOfRetVal},
663 {
"rtfinclude", endOfLine },
664 {
"section", endOfLabel },
665 {
"skip", endOfLine },
666 {
"skipline", endOfLine },
667 {
"snippet", endOfLine },
668 {
"snippetdoc", endOfLine },
669 {
"snippetlineno", endOfLine },
670 {
"struct", endOfLine },
671 {
"subpage", endOfLabel },
672 {
"subparagraph", endOfLabel },
673 {
"subsubparagraph",endOfLabel },
674 {
"subsection", endOfLabel },
675 {
"subsubsection", endOfLabel },
676 {
"throw", endOfLabel },
677 {
"throws", endOfLabel },
678 {
"tparam", endOfLabel },
679 {
"typedef", endOfLine },
680 {
"plantumlfile", endOfLine },
681 {
"union", endOfLine },
682 {
"until", endOfLine },
683 {
"var", endOfLine },
684 {
"verbinclude", endOfLine },
685 {
"weakgroup", endOfLabel },
686 {
"xmlinclude", endOfLine },
687 {
"xrefitem", endOfLabel }
690 bool isEscaped = offset>0 && (data.data()[-1]==
'\\' || data.data()[-1]==
'@');
691 if (isEscaped)
return 0;
693 const size_t size = data.size();
695 while (
end<size && (data[
end]>=
'a' && data[
end]<=
'z'))
end++;
696 if (
end==1)
return 0;
697 std::string cmdName(data.substr(1,
end-1));
699 auto it = cmdNames.find(cmdName);
700 if (it!=cmdNames.end())
703 result = it->second(data,
end);
716 const size_t size = data.size();
720 while (i<size && data[i]!=c &&
721 data[i]!=
'\\' && data[i]!=
'@' &&
722 !(data[i]==
'/' && data[i-1]==
'<') &&
742 while (i+len<size && data[i+len]==c)
749 if (len!=c_size || (i+len<size &&
isIdChar(data[i+len])))
755 return static_cast<int>(i);
763 while (i<size && data[i]==
'`') snb++,i++;
767 while (i<size && enb<snb)
769 if (data[i]==
'`') enb++;
770 if (snb==1 && data[i]==
'\'')
break;
774 else if (data[i]==
'@' || data[i]==
'\\')
780 size_t l = endBlockName.
length();
783 if ((data[i]==
'\\' || data[i]==
'@') &&
784 data[i-1]!=
'\\' && data[i-1]!=
'@')
794 else if (i+1<size &&
isIdChar(data[i+1]))
803 else if (data[i-1]==
'<' && data[i]==
'/')
807 else if (data[i]==
'\n')
810 while (i<size && data[i]==
' ') i++;
811 if (i>=size || data[i]==
'\n')
829 const size_t size = data.size();
832 if (size>1 && data[0]==c && data[1]==c) { i=1; }
837 if (len==0) {
return 0; }
839 if (i>=size) {
return 0; }
841 if (i+1<size && data[i+1]==c)
846 if (data[i]==c && data[i-1]!=
' ' && data[i-1]!=
'\n')
852 return static_cast<int>(i+1);
863 const size_t size = data.size();
873 if (i+1<size && data[i]==c && data[i+1]==c && i && data[i-1]!=
' ' && data[i-1]!=
'\n')
875 if (c ==
'~')
out+=
"<strike>";
876 else out+=
"<strong>";
878 if (c ==
'~')
out+=
"</strike>";
879 else out+=
"</strong>";
881 return static_cast<int>(i+2);
895 const size_t size = data.size();
907 if (data[i]!=c || data[i-1]==
' ' || data[i-1]==
'\n')
912 if (i+2<size && data[i+1]==c && data[i+2]==c)
916 out+=
"</strong></em>";
918 return static_cast<int>(i+3);
920 else if (i+1<size && data[i+1]==c)
931 return static_cast<int>(len - 2);
945 return static_cast<int>(len - 1);
956 const size_t size = data.size();
960 if (i<size && data[i]==
'-')
964 if (i<size && data[i]==
'-')
968 if (i<size && data[i]==
'-')
972 if (count>=2 && offset>=2 &&
literal_at(data.data()-2,
"<!"))
974 if (count==2 && size > 2 && data[2]==
'>')
976 if (count==3 && size > 3 && data[3]==
'>')
978 if (count==2 && (offset<8 || !
literal_at(data.data()-8,
"operator")))
998 const size_t size = data.size();
1001 while (i<size && data[i]!=
'"' && nl<2)
1003 if (data[i]==
'\n') nl++;
1006 if (i<size && data[i]==
'"' && nl<2)
1008 out+=data.substr(0,i+1);
1010 return static_cast<int>(i+1);
1022 if (offset>0 && data.data()[-1]==
'\\') {
return 0; }
1024 const size_t size = data.size();
1030 while (i<size &&
isIdChar(data[i])) i++,l++;
1031 QCString tagName(data.substr(1,i-1));
1032 if (tagName.
lower()==
"pre")
1034 bool insideStr=
FALSE;
1038 if (!insideStr && c==
'<')
1040 if (data[i+1]==
'/' &&
1041 tolower(data[i+2])==
'p' && tolower(data[i+3])==
'r' &&
1042 tolower(data[i+4])==
'e' && tolower(data[i+5])==
'>')
1044 if (doWrite)
out+=data.substr(0,i+6);
1047 return static_cast<int>(i+6);
1050 else if (insideStr && c==
'"')
1052 if (data[i-1]!=
'\\') insideStr=
FALSE;
1065 if (data[i]==
'/' && i+1<size && data[i+1]==
'>')
1068 if (doWrite)
out+=data.substr(0,i+2);
1070 return static_cast<int>(i+2);
1072 else if (data[i]==
'>')
1075 if (doWrite)
out+=data.substr(0,i+1);
1077 return static_cast<int>(i+1);
1079 else if (data[i]==
' ')
1082 bool insideAttr=
FALSE;
1085 if (!insideAttr && data[i]==
'"')
1089 else if (data[i]==
'"' && data[i-1]!=
'\\')
1093 else if (!insideAttr && data[i]==
'>')
1096 if (doWrite)
out+=data.substr(0,i+1);
1098 return static_cast<int>(i+1);
1118 const size_t size = data.size();
1130 if (size>2 && c!=
'~' && data[1]!=c)
1133 if (data[1]==
' ' || data[1]==
'\n' ||
1141 if (size>3 && data[1]==c && data[2]!=c)
1143 if (data[2]==
' ' || data[2]==
'\n' ||
1151 if (size>4 && c!=
'~' && data[1]==c && data[2]==c && data[3]!=c)
1153 if (data[3]==
' ' || data[3]==
'\n' ||
1165 std::string_view
fmt,
bool inline_img,
bool explicitTitle,
1170 AUTO_TRACE(
"fmt={} inline_img={} explicitTitle={} title={} content={} link={} attrs={}",
1181 out+=link.
mid(fd ? 0 : 5);
1182 if (!explicitTitle && !content.
isEmpty())
1188 else if ((content.
isEmpty() || explicitTitle) && !title.
isEmpty())
1210 const size_t size = data.size();
1214 bool isImageLink =
FALSE;
1215 bool isImageInline =
FALSE;
1221 if (size<2 || data[1]!=
'[')
1230 while (pos>=-
static_cast<int>(offset) && numNLsNeeded>0)
1232 if (data.data()[pos]==
'\n') numNLsNeeded--;
1233 else if (data.data()[pos]!=
' ')
1243 size_t contentStart=i;
1250 if (data[i-1]==
'\\')
1253 else if (data[i]==
'[')
1257 else if (data[i]==
']')
1260 if (level<=0)
break;
1262 else if (data[i]==
'\n')
1265 if (nl>1) {
return 0; }
1271 if (i>=size)
return 0;
1272 size_t contentEnd=i;
1273 content = data.substr(contentStart,contentEnd-contentStart);
1275 if (!isImageLink && content.
isEmpty()) {
return 0; }
1278 bool whiteSpace =
false;
1280 while (i<size && data[i]==
' ') { whiteSpace =
true; i++; }
1281 if (i<size && data[i]==
'\n')
1286 while (i<size && data[i]==
' ') i++;
1288 if (whiteSpace && i<size && (data[i]==
'(' || data[i]==
'['))
return 0;
1290 bool explicitTitle=
FALSE;
1291 if (i<size && data[i]==
'(')
1294 while (i<size && data[i]==
' ') i++;
1295 bool uriFormat=
false;
1296 if (i<size && data[i]==
'<') { i++; uriFormat=
true; }
1299 while (i<size && data[i]!=
'\'' && data[i]!=
'"' && braceCount>0)
1304 if (nl>1) {
return 0; }
1306 else if (data[i]==
'(')
1310 else if (data[i]==
')')
1321 if (i>=size || data[i]==
'\n') {
return 0; }
1322 link = data.substr(linkStart,i-linkStart);
1325 if (link.
isEmpty()) {
return 0; }
1329 if (data[i]==
'\'' || data[i]==
'"')
1333 size_t titleStart=i;
1339 if (nl>1) {
return 0; }
1342 else if (data[i]==
'\\')
1346 else if (data[i]==c)
1357 size_t titleEnd = i-1;
1359 while (titleEnd>titleStart && data[titleEnd]==
' ') titleEnd--;
1360 if (data[titleEnd]==c)
1362 title = data.substr(titleStart,titleEnd-titleStart);
1366 if (data[i]==
' ')i++;
1367 else if (data[i] ==
')')
break;
1381 else if (i<size && data[i]==
'[')
1387 while (i<size && data[i]!=
']')
1392 if (nl>1) {
return 0; }
1396 if (i>=size) {
return 0; }
1398 link = data.substr(linkStart,i-linkStart);
1410 link = lr_it->second.link;
1411 title = lr_it->second.title;
1421 else if (i<size && data[i]!=
':' && !content.
isEmpty())
1428 link = lr_it->second.link;
1429 title = lr_it->second.title;
1433 else if (content==
"TOC")
1456 while (j<size && data[j]==
' ') { j++; }
1457 if (j<size && data[j]==
'{')
1462 size_t attributesStart=i;
1467 if (data[i-1]==
'\\')
1470 else if (data[i]==
'{')
1474 else if (data[i]==
'}')
1477 if (level<=0)
break;
1479 else if (data[i]==
'\n')
1482 if (nl>1) {
return 0; }
1487 if (i>=size)
return 0;
1488 size_t attributesEnd=i;
1489 attributes = data.substr(attributesStart,attributesEnd-attributesStart);
1498 while (pos<size && numNLsNeeded>0)
1500 if (data[pos]==
'\n') numNLsNeeded--;
1501 else if (data[pos]!=
' ')
1516 out+=
"@tableofcontents{html:";
1521 else if (isImageLink)
1525 if (link.
find(
"@ref ")!=-1 || link.
find(
"\\ref ")!=-1 ||
1530 writeMarkdownImage(
"html", isImageInline, explicitTitle, title, content, link, attributes, fd);
1531 writeMarkdownImage(
"latex", isImageInline, explicitTitle, title, content, link, attributes, fd);
1532 writeMarkdownImage(
"rtf", isImageInline, explicitTitle, title, content, link, attributes, fd);
1533 writeMarkdownImage(
"docbook", isImageInline, explicitTitle, title, content, link, attributes, fd);
1534 writeMarkdownImage(
"xml", isImageInline, explicitTitle, title, content, link, attributes, fd);
1556 if ((lp=link.
find(
"@ref "))!=-1 || (lp=link.
find(
"\\ref "))!=-1 || (lang==SrcLangExt::Markdown && !
isURL(link)))
1588 if (explicitTitle && !title.
isEmpty())
1598 else if ((lp=link.
find(
'#'))!=-1 || link.
find(
'/')!=-1 || link.
find(
'.')!=-1)
1600 if (lp==0 || (lp>0 && !
isURL(link) &&
Config_getEnum(MARKDOWN_ID_STYLE)==MARKDOWN_ID_STYLE_t::GITHUB))
1613 for (
int ii = 0; ii < nlTotal; ii++)
out+=
"\n";
1635 return static_cast<int>(i);
1642 const size_t size = data.size();
1646 while (nb<size && data[nb]==
'`')
1663 if (
end+1<size && data[
end+1]==
'`')
1678 if (
end+1<size && data[
end+1]==
'`')
1691 else if (data[
end]==
'\n')
1702 else if (!markdownStrict && data[
end]==
'\'' && nb==1 && (
end+1==size || (
end+1<size && data[
end+1]!=
'\'' && !
isIdChar(data[
end+1]))))
1705 out+=data.substr(nb,
end-nb);
1708 return static_cast<int>(
end+1);
1710 else if (!markdownStrict && data[
end]==
'\'' && nb==2 &&
end+1<size && data[
end+1]==
'\'')
1713 out+=data.substr(nb,
end-nb);
1716 return static_cast<int>(
end+2);
1720 if (data[
end]!=
' ') pc = data[
end];
1724 if (i < nb && end >= size)
1729 out+=data.substr(0,nb);
1734 while (
end<size && data[
end]==
'`')
1744 QCString codeFragment = data.substr(nb,
end-nb-nb);
1750 return static_cast<int>(
end);
1769 const size_t size = data.size();
1775 size_t l = endBlockName.
length();
1778 if ((data[i]==
'\\' || data[i]==
'@') &&
1779 data[i-1]!=
'\\' && data[i-1]!=
'@')
1786 return static_cast<int>(i+1+l);
1795 out+=data.substr(0,endPos);
1796 return static_cast<int>(endPos);
1798 if (size>1 && data[0]==
'\\')
1801 if (c==
'[' || c==
']' || c==
'*' || c==
'(' || c==
')' || c==
'`' || c==
'_')
1807 else if (c==
'\\' || c==
'@')
1809 out+=data.substr(0,2);
1813 else if (c==
'-' && size>3 && data[2]==
'-' && data[3]==
'-')
1815 out+=data.substr(1,3);
1819 else if (c==
'-' && size>2 && data[2]==
'-')
1821 out+=data.substr(1,2);
1826 else if (size>1 && data[0]==
'@')
1829 if (c==
'\\' || c==
'@')
1831 out+=data.substr(0,2);
1845 const size_t size = data.size();
1849 while (
end<size && ((action=
actions[
static_cast<uint8_t
>(data[
end])])==
nullptr))
end++;
1851 out+=data.substr(i,
end-i);
1852 if (
end>=size)
break;
1855 int iend = action(data.substr(i),i);
1873 const size_t size = data.size();
1874 while (i<size && data[i]==
' ') i++;
1875 if (i==size)
return 0;
1880 while (i<size && data[i]==
'=') i++,c++;
1881 while (i<size && data[i]==
' ') i++;
1882 int level = (c>1 && (i>=size || data[i]==
'\n')) ? 1 : 0;
1883 if (allowAdjustLevel && level==1 &&
indentLevel==-1)
1898 while (i<size && data[i]==
'-') i++,c++;
1899 while (i<size && data[i]==
' ') i++;
1900 return (c>1 && (i>=size || data[i]==
'\n')) ?
indentLevel+2 : 0;
1910 const size_t size = data.size();
1911 while (i<size && data[i]==
' ') i++;
1916 while (i<size && (data[i]==
'>' || data[i]==
' '))
1918 if (data[i]==
'>') level++;
1923 bool res = (level>0 && i<size && ((data[i-1]==
' ') || data[i]==
'\n')) || (level > 1);
1938 const size_t size = data.size();
1941 while (i<size && data[i]==
' ') i++;
1942 if (i>=size || data[i]!=
'[') {
return 0; }
1944 size_t refIdStart=i;
1945 while (i<size && data[i]!=
'\n' && data[i]!=
']') i++;
1946 if (i>=size || data[i]!=
']') {
return 0; }
1947 refid = data.substr(refIdStart,i-refIdStart);
1948 if (refid.
isEmpty()) {
return 0; }
1952 if (i>=size || data[i]!=
':') {
return 0; }
1956 while (i<size && data[i]==
' ') i++;
1957 if (i<size && data[i]==
'\n')
1960 while (i<size && data[i]==
' ') i++;
1962 if (i>=size) {
return 0; }
1964 if (i<size && data[i]==
'<') i++;
1966 while (i<size && data[i]!=
' ' && data[i]!=
'\n') i++;
1968 if (i<size && data[i]==
'>') i++;
1969 if (linkStart==linkEnd) {
return 0; }
1970 link = data.substr(linkStart,linkEnd-linkStart);
1972 if (link==
"@ref" || link==
"\\ref")
1975 while (i<size && data[i]!=
'\n' && data[i]!=
'"') i++;
1976 link+=data.substr(argStart,i-argStart);
1983 while (i<size && data[i]==
' ') i++;
1984 if (i<size && data[i]==
'\n')
1988 while (i<size && data[i]==
' ') i++;
1992 AUTO_TRACE_EXIT(
"result={}: end of isLinkRef while looking for title",i);
1997 if (c==
'\'' || c==
'"' || c==
'(')
2002 size_t titleStart=i;
2004 while (i<size && data[i]!=
'\n') i++;
2009 while (
end>titleStart && data[
end]!=c)
end--;
2012 title = data.substr(titleStart,
end-titleStart);
2016 while (i<size && data[i]==
' ') i++;
2028 size_t size = data.size();
2029 if (size>0 && data[size-1]==
'\n') size--;
2030 while (i<size && data[i]==
' ') i++;
2031 if (i>=size) {
AUTO_TRACE_EXIT(
"result=false: empty line");
return false; }
2033 if (c!=
'*' && c!=
'-' && c!=
'_')
2045 else if (data[i]!=
' ')
2060 static const reg::Ex r2(R
"({#(\a[\w-]*)}\s*$)");
2062 std::string ti = title.str();
2065 std::string
id = match[1].str();
2066 title = title.
left(match.position());
2069 warn(
fileName,
lineNr,
"An automatically generated id already has the name '{}'!",
id);
2075 if (((level>0) && (level<=
Config_getInt(TOC_INCLUDE_HEADINGS))) || (
Config_getEnum(MARKDOWN_ID_STYLE)==MARKDOWN_ID_STYLE_t::GITHUB))
2078 if (pIsIdGenerated) *pIsIdGenerated=
true;
2093 int level = 0, blanks=0;
2094 const size_t size = data.size();
2097 while (i<size && data[i]==
' ') i++;
2098 if (i>=size || data[i]!=
'#')
2102 while (i<size && data[i]==
'#') i++,level++;
2107 while (i<size && data[i]==
' ') i++,blanks++;
2108 if (level==1 && blanks==0)
2115 while (
end<size && data[
end]!=
'\n')
end++;
2116 while (
end>i && (data[
end-1]==
'#' || data[
end-1]==
' '))
end--;
2119 header = data.substr(i,
end-i);
2123 int idx=
static_cast<int>(header.
length())-1;
2124 while (idx>=0 && (header.
at(idx)==
'#' || header.
at(idx)==
' ')) idx--;
2125 header=header.
left(idx+1);
2128 if (allowAdjustLevel && level==1 &&
indentLevel==-1)
2159 while (i<data.size())
2170 (data[(i)]=='<' && \
2171 (data[(i)+1]=='l' || data[(i)+1]=='L') && \
2172 (data[(i)+2]=='i' || data[(i)+2]=='I') && \
2181 const size_t size=data.size();
2185 bool listMarkerSkipped=
FALSE;
2188 (!listMarkerSkipped &&
2189 (data[i]==
'+' || data[i]==
'-' || data[i]==
'*' ||
2190 (data[i]==
'#' && i>0 && data[i-1]==
'-') ||
2191 (isDigit=(data[i]>=
'1' && data[i]<=
'9')) ||
2192 (isLi=(size>=3 && i+3<size &&
isLiTag(i)))
2201 while (j<size && ((data[j]>=
'0' && data[j]<=
'9') || data[j]==
'.'))
2205 if (j+1<size && data[j+1]==
' ')
2207 listMarkerSkipped=
TRUE;
2224 listMarkerSkipped=
TRUE;
2226 else if (data[i]==
'-' && size>=2 && i+2<size && data[i+1]==
'#' && data[i+2]==
' ')
2228 listMarkerSkipped=
TRUE;
2232 else if (data[i]!=
' ' && i+1<size && data[i+1]==
' ')
2234 listMarkerSkipped=
TRUE;
2236 if (data[i]!=
' ' && !listMarkerSkipped)
2249 size_t normalIndent = 0;
2250 while (normalIndent<data.size() && data[normalIndent]==
' ') normalIndent++;
2252 size_t result = listIndent>normalIndent ? listIndent : 0;
2263 while (i<data.size())
2269 else if (data[i]==
'\n')
2273 else if (data[i]!=
' ' && data[i]!=
'\t')
2285 QCString &lang,
size_t &start,
size_t &
end,
size_t &offset)
2288 const char dot =
'.';
2289 auto isAlphaChar = [ ](
char c) {
return (c>=
'A' && c<=
'Z') || (c>=
'a' && c<=
'z'); };
2290 auto isAlphaNChar = [ ](
char c) {
return (c>=
'A' && c<=
'Z') || (c>=
'a' && c<=
'z') || (c>=
'0' && c<=
'9') || (c==
'+'); };
2291 auto isLangChar = [&](
char c) {
return c==dot || isAlphaChar(c); };
2297 const size_t size = data.size();
2298 while (i<size && data[i]==
' ') indent++,i++;
2299 if (indent>=refIndent+4)
2301 AUTO_TRACE_EXIT(
"result=false: content is part of code block indent={} refIndent={}",indent,refIndent);
2305 if (i<size && data[i]==
'`') tildaChar=
'`';
2306 while (i<size && data[i]==tildaChar) startTildes++,i++;
2309 AUTO_TRACE_EXIT(
"result=false: no fence marker found #tildes={}",startTildes);
2312 if (i<size && data[i]==
'{')
2315 if (data[i] == dot) i++;
2317 while (i<size && (data[i]!=
'\n' && data[i]!=
'}')) i++;
2318 if (i<size && data[i]==
'}')
2320 lang = data.substr(startLang,i-startLang);
2329 else if (i<size && isLangChar(data[i]))
2331 if (data[i] == dot) i++;
2333 if (i<size && isAlphaChar(data[i]))
2336 while (i<size && isAlphaNChar(data[i])) i++;
2338 lang = data.substr(startLang,i-startLang);
2348 if (data[i]==tildaChar)
2352 while (i<size && data[i]==tildaChar) endTildes++,i++;
2353 while (i<size && data[i]==
' ') i++;
2355 if (endTildes==startTildes)
2358 AUTO_TRACE_EXIT(
"result=true: found end marker at offset {} lang='{}'",offset,lang);
2369static bool isCodeBlock(std::string_view data,
size_t offset,
size_t &indent)
2376 const size_t size = data.size();
2377 while (i<size && data[i]==
' ') indent0++,i++;
2381 AUTO_TRACE_EXIT(
"result={}: line is not indented enough {}<4",
false,indent0);
2384 if (indent0>=size || data[indent0]==
'\n')
2386 AUTO_TRACE_EXIT(
"result={}: only spaces at the end of a comment block",
false);
2393 int offset_i =
static_cast<int>(offset);
2397 int j =
static_cast<int>(i)-offset_i-1;
2399 size_t nl_size =
isNewline(std::string_view(data.data()+j,data.size()-j));
2402 nl_pos[nl++]=j+
static_cast<int>(nl_size);
2408 if (i==0 && nl==2) nl_pos[nl++]=-offset_i;
2419 if (!
isEmptyLine(std::string_view(data.data()+nl_pos[1],nl_pos[0]-nl_pos[1]-1)))
2428 std::string_view(data.data()+nl_pos[2],nl_pos[1]-nl_pos[2])));
2434 AUTO_TRACE_EXIT(
"result={}: code block if indent difference >4 spaces",res);
2441 if (nl==1 && !
isEmptyLine(std::string_view(data.data()-offset,offset-1)))
2449 AUTO_TRACE_EXIT(
"result={}: code block if indent difference >4 spaces",res);
2465 const size_t size = data.size();
2468 while (i<size && data[i]==
' ') i++;
2469 if (i<size && data[i]==
'|' && data[i]!=
'\n') i++,n++;
2474 while (i<size && (j =
isNewline(data.substr(i)))==0) i++;
2477 if (j>0 && i>0) i--;
2478 while (i>0 && data[i]==
' ') i--;
2479 if (i>0 && data[i-1]!=
'\\' && data[i]==
'|') i--,n++;
2489 if (data[i]==
'|' && (i==0 || data[i-1]!=
'\\')) columns++;
2490 if (columns==1) columns++;
2494 if (n==2 && columns==0)
2506 size_t cc0=0, start=0,
end=0;
2510 if (i>=data.size() || cc0<1)
2522 if (data[j]!=
':' && data[j]!=
'-' && data[j]!=
'|' && data[j]!=
' ')
2531 AUTO_TRACE_EXIT(
"result=false: different number of columns as previous line {}!={}",cc1,cc0);
2546 const size_t size = data.size();
2548 size_t columns=0, start=0,
end=0;
2550 size_t headerStart = start;
2551 size_t headerEnd =
end;
2557 std::vector<int> columnAlignment(columns);
2559 bool leftMarker=
false, rightMarker=
false, startFound=
false;
2565 if (data[j]==
':') { leftMarker=
TRUE; startFound=
TRUE; }
2566 if (data[j]==
'-') startFound=
TRUE;
2569 if (data[j]==
'-') rightMarker=
FALSE;
2570 else if (data[j]==
':') rightMarker=
TRUE;
2571 if (j<=
end+i && (data[j]==
'|' && (j==0 || data[j-1]!=
'\\')))
2595 std::vector<std::vector<TableCell> > tableContents;
2597 size_t m = headerStart;
2598 std::vector<TableCell> headerContents(columns);
2599 for (k=0;k<columns;k++)
2601 while (m<=headerEnd && (data[m]!=
'|' || (m>0 && data[m-1]==
'\\')))
2603 headerContents[k].cellText += data[m++];
2608 headerContents[k].colSpan = headerContents[k].cellText.isEmpty();
2609 headerContents[k].cellText = headerContents[k].cellText.stripWhiteSpace();
2611 tableContents.push_back(headerContents);
2617 if (cc!=columns)
break;
2621 std::vector<TableCell> rowContents(columns);
2624 if (j<=
end+i && (data[j]==
'|' && (j==0 || data[j-1]!=
'\\')))
2628 rowContents[k].colSpan = rowContents[k].cellText.isEmpty();
2629 rowContents[k].cellText = rowContents[k].cellText.stripWhiteSpace();
2634 rowContents[k].cellText += data[j];
2640 rowContents[k].colSpan = rowContents[k].cellText.isEmpty();
2641 rowContents[k].cellText = rowContents[k].cellText.stripWhiteSpace();
2642 tableContents.push_back(rowContents);
2648 out+=
"<table class=\"markdownTable\">";
2649 QCString cellTag(
"th"), cellClass(
"class=\"markdownTableHead");
2650 for (
size_t row = 0; row < tableContents.size(); row++)
2656 out+=
"\n<tr class=\"markdownTableRowOdd\">";
2660 out+=
"\n<tr class=\"markdownTableRowEven\">";
2665 out+=
"\n <tr class=\"markdownTableHead\">";
2667 for (
size_t c = 0; c < columns; c++)
2670 QCString cellText(tableContents[row][c].cellText);
2676 if (tableContents[row][c].cellText ==
"^")
2680 if (tableContents[row][c].colSpan)
2682 int cr =
static_cast<int>(c);
2683 while ( cr >= 0 && tableContents[row][cr].colSpan)
2687 if (cr >= 0 && tableContents[row][cr].cellText ==
"^")
continue;
2689 size_t rowSpan = 1, spanRow = row+1;
2690 while ((spanRow < tableContents.size()) &&
2691 (tableContents[spanRow][c].cellText ==
"^"))
2697 out+=
" <" + cellTag +
" " + cellClass;
2699 switch (columnAlignment[c])
2711 out+=
" rowspan=\"" + spanStr +
"\"";
2717 while ((c+1 < columns) && tableContents[row][c+1].colSpan)
2726 out+=
" colspan=\"" + spanStr +
"\"";
2730 out+=
"> " + cellText +
" \\ilinebr </" + cellTag +
">";
2733 cellClass =
"class=\"markdownTableBody";
2749 while (i<data.size() && data[i]!=
'\n')
2751 if (data[i]!=
' ' && data[i]!=
'\t') j++;
2754 if (i>=data.size()) {
return 0; }
2755 if (i<2) {
return 0; }
2756 bool res = (j>0 && data[i-1]==
' ' && data[i-2]==
' ');
2796 out+=
"</"+hTag+
">\n";
2799 else if (data.size()>0)
2801 size_t tmpSize = data.size();
2802 if (data[data.size()-1] ==
'\n') tmpSize--;
2803 out+=data.substr(0,tmpSize);
2807 out+=
"\\ilinebr<br>";
2809 if (tmpSize != data.size())
out+=
'\n';
2815 {
"[!note]",
"\\note" },
2816 {
"[!warning]",
"\\warning" },
2817 {
"[!tip]",
"\\remark" },
2818 {
"[!caution]",
"\\attention" },
2819 {
"[!important]",
"\\important" }
2828 const size_t size = data.size();
2829 std::string startCmd;
2830 int isGitHubAlert =
false;
2831 int isGitHubFirst =
false;
2836 while (
end<=size && data[
end-1]!=
'\n')
end++;
2841 while (j<
end && (data[j]==
' ' || data[j]==
'>'))
2843 if (data[j]==
'>') { level++; indent=j+1; }
2844 else if (j>0 && data[j-1]==
'>') indent=j+1;
2847 if (indent>0 && j>0 && data[j-1]==
'>' &&
2848 !(j==size || data[j]==
'\n'))
2865 isGitHubAlert =
true;
2866 isGitHubFirst =
true;
2867 startCmd = it->second;
2872 if (level!=1 || !isGitHubAlert)
2874 for (
int l=curLevel;l<level-1;l++)
2876 out+=
"<blockquote>";
2878 out +=
"<blockquote>‍";
2880 else if (!startCmd.empty())
2882 out += startCmd +
" ";
2885 else if (level<curLevel)
2888 if (level==0 && isGitHubAlert)
2894 out +=
"</blockquote>\\ilinebr ";
2903 if (curLevel!=0 || !isGitHubAlert)
2905 std::string_view txt = data.substr(indent,
end-indent);
2908 if (!isGitHubFirst)
out +=
"<br>";
2915 isGitHubFirst =
false;
2930 for (
int l=0;l<curLevel;l++)
2932 out+=
"</blockquote>";
2943 size_t size = data.size();
2944 while (i<data.size() && data[i]==
' ') i++;
2947 size_t locStart = i;
2948 if (i>offset) locStart--;
2951 while (i+9<size && data[i]!=
'\n')
2963 location=data.substr(locStart,i-locStart);
2965 while (indent>0 && i<size && data[i]==
' ') i++,indent--;
2966 if (i<size && data[i]==
'\n') i++;
2977 const size_t size = data.size();
2980 out+=
"@iverbatim\n";
2982 std::string location;
2987 while (
end<=size && data[
end-1]!=
'\n')
end++;
2990 while (j<
end && data[j]==
' ') j++,indent++;
3000 while (emptyLines>0)
3008 std::string lineLoc;
3013 out+=data.substr(offset,
end-offset);
3021 out+=
"@endiverbatim";
3022 if (!location.empty())
3030 while (emptyLines>0)
3046 const size_t size = data.size();
3047 size_t nb=0,
end=offset+1, j=0;
3052 if ((data[
end-1]==
'\\' || data[
end-1]==
'@') &&
3053 (
end<=1 || (data[
end-2]!=
'\\' && data[
end-2]!=
'@'))
3060 size_t l = endBlockName.
length();
3061 for (;
end+l+1<size;
end++)
3063 if ((data[
end]==
'\\' || data[
end]==
'@') &&
3064 data[
end-1]!=
'\\' && data[
end-1]!=
'@'
3078 else if (nb==0 && data[
end-1]==
'<' && size>=6 &&
end+6<size &&
3079 (
end<=1 || (data[
end-2]!=
'\\' && data[
end-2]!=
'@'))
3082 if (tolower(data[
end])==
'p' && tolower(data[
end+1])==
'r' &&
3083 tolower(data[
end+2])==
'e' && (data[
end+3]==
'>' || data[
end+3]==
' '))
3094 else if (nb==0 && data[
end-1]==
'`')
3096 while (
end<=size && data[
end-1]==
'`')
end++,nb++;
3098 else if (nb>0 && data[
end-1]==
'`')
3101 while (
end<=size && data[
end-1]==
'`')
end++,enb++;
3115 size_t blockStart,
size_t blockEnd)
3118 if (!lang.empty() && lang[0]==
'.') lang=lang.substr(1);
3119 const size_t size=data.size();
3121 while (i<size && (data[i]==
' ' || data[i]==
'\t'))
3142 size_t pi=std::string::npos;
3143 bool newBlock =
false;
3144 bool insideList =
false;
3145 size_t currentIndent = refIndent;
3146 size_t listIndent = refIndent;
3147 const size_t size = data.size();
3154 size_t lineIndent=0;
3155 while (lineIndent<
end && data[i+lineIndent]==
' ') lineIndent++;
3161 if (insideList && lineIndent<currentIndent)
3164 currentIndent = refIndent;
3172 if (listIndent<currentIndent+4)
3176 currentIndent = listIndent;
3183 currentIndent = listIndent;
3192 if (pi!=std::string::npos)
3194 size_t blockStart=0, blockEnd=0, blockOffset=0;
3195 if (
isFencedCodeBlock(data.substr(pi),currentIndent,lang,blockStart,blockEnd,blockOffset))
3197 auto addSpecialCommand = [&](
const QCString &startCmd,
const QCString &endCmd)
3199 size_t cmdPos = pi+blockStart+1;
3200 QCString pl = data.substr(cmdPos,blockEnd-blockStart-1);
3206 if (pl[ii]==
'\n') nl++;
3209 bool addNewLines =
false;
3211 (pl[ii]!=
'\\' && pl[ii]!=
'@') ||
3220 pl =
"@"+startCmd+
"\n" + pl +
"@"+endCmd;
3221 addNewLines =
false;
3236 if (addNewLines)
for (
int j=0;j<nl;j++)
out+=
'\n';
3238 if (addNewLines)
out+=
'\n';
3243 addSpecialCommand(
"startuml",
"enduml");
3247 addSpecialCommand(
"dot",
"enddot");
3249 else if (lang==
"msc")
3251 addSpecialCommand(
"msc",
"endmsc");
3258 pi=std::string::npos;
3262 else if (
isBlockQuote(data.substr(pi,i-pi),currentIndent))
3265 pi=std::string::npos;
3272 out+=data.substr(pi,i-pi);
3278 if (pi!=std::string::npos && pi<size)
3286 out+=data.substr(pi);
3300 size_t pi = std::string::npos;
3316 size_t currentIndent = indent;
3317 size_t listIndent = indent;
3318 bool insideList =
false;
3319 bool newBlock =
false;
3322 while (i<data.size())
3327 size_t lineIndent=0;
3329 while (lineIndent<
end && data[i+lineIndent]==
' ') lineIndent++;
3335 if (insideList && lineIndent<currentIndent)
3338 currentIndent = indent;
3346 if (listIndent<currentIndent+4)
3350 currentIndent = listIndent;
3357 currentIndent = listIndent;
3369 if (pi!=std::string::npos)
3371 size_t blockStart=0, blockEnd=0, blockOffset=0;
3373 size_t blockIndent = currentIndent;
3377 if (data[i]==
'@' || data[i]==
'\\') endBlockName =
isBlockCommand(data.substr(i),i);
3381 if (
isLinkRef(data.substr(pi,i-pi),
id,link,title))
3391 size_t l = endBlockName.
length();
3392 while (i+l<data.size())
3394 if ((data[i]==
'\\' || data[i]==
'@') &&
3395 data[i-1]!=
'\\' && data[i-1]!=
'@')
3412 while (pi<data.size() && data[pi]==
' ') pi++;
3413 QCString header = data.substr(pi,i-pi-1);
3420 out+=level==1?
"@section ":
"@subsection ";
3428 out+=level==1?
"<h1>":
"<h2>";
3430 out+=level==1?
"\n</h1>\n":
"\n</h2>\n";
3437 pi=std::string::npos;
3442 else if ((ref=
isLinkRef(data.substr(pi),
id,link,title)))
3450 else if (
isFencedCodeBlock(data.substr(pi),currentIndent,lang,blockStart,blockEnd,blockOffset))
3456 pi=std::string::npos;
3464 pi=std::string::npos;
3471 pi=std::string::npos;
3484 if (pi!=std::string::npos && pi<data.size())
3486 if (
isLinkRef(data.substr(pi),
id,link,title))
3503#define OPC(x) if (literal_at(data,#x " ") || literal_at(data,#x "\n")) return true
3504 OPC(dir);
OPC(defgroup);
OPC(addtogroup);
OPC(weakgroup);
OPC(ingroup);
3507 OPC(protocol);
OPC(category);
OPC(
union);
OPC(
struct);
OPC(interface);
3508 OPC(idlexcept);
OPC(file);
3518 std::string_view data(docs.
str());
3519 const size_t size = data.size();
3522 while (i<size && (data[i]==
' ' || data[i]==
'\n'))
3529 while (i<size && (data[i]==
' ' || data[i]==
'\n'))
3535 (data[i]==
'\\' || data[i]==
'@') &&
3550 else if (i+1<size && (data[i]==
'\\' || data[i]==
'@') &&
isOtherPage(data.substr(i+1)))
3568 std::string_view data(docs_org.
str());
3569 const size_t size = data.size();
3571 while (i<size && (data[i]==
' ' || data[i]==
'\n'))
3573 if (data[i]==
'\n') prepend++;
3576 if (i>=size) {
return QCString(); }
3578 while (end1<size && data[end1-1]!=
'\n') end1++;
3585 while (end2<size && data[end2-1]!=
'\n') end2++;
3586 if (
prv->isHeaderline(data.substr(end1),
FALSE))
3588 title = data.substr(i,end1-i-1);
3589 docs+=
"\n\n"+docs_org.
mid(end2);
3590 id =
prv->extractTitleId(title, 0, &isIdGenerated);
3596 if (i<end1 && prv->isAtxHeader(data.substr(i,end1-i),title,
id,
FALSE,&isIdGenerated)>0)
3599 docs+=docs_org.
mid(end1);
3604 id =
prv->extractTitleId(title, 0, &isIdGenerated);
3615 if (input.
isEmpty())
return input;
3620 if (s.
at(s.
length()-1)!=
'\n') s +=
"\n";
3621 s =
detab(s,refIndent);
3625 s =
prv->processQuotations(s.
view(),refIndent);
3629 s =
prv->processBlocks(s.
view(),refIndent);
3647 const char *p = result.
data();
3650 while (*p==
' ') p++;
3651 while (*p==
'\n') {startNewlines++;p++;};
3654 if (p>result.
data())
3657 result = result.
mid(
static_cast<int>(p-result.
data()));
3670 if (i!=-1) baseFn = baseFn.
left(i);
3694 const char *fileBuf,
3695 const std::shared_ptr<Entry> &root,
3698 std::shared_ptr<Entry> current = std::make_shared<Entry>();
3700 current->lang = SrcLangExt::Markdown;
3701 current->fileName = fileName;
3702 current->docFile = fileName;
3703 current->docLine = 1;
3709 bool isIdGenerated =
false;
3717 int indentLevel=title.
isEmpty() ? 0 : -1;
3724 bool wasEmpty =
id.isEmpty();
3725 if (wasEmpty)
id = mdFileNameId;
3731 if (!mdfileAsMainPage.
isEmpty() &&
3735 docs.
prepend(
"@ianchor{" + title +
"} " +
id +
"\\ilinebr ");
3736 docs.
prepend(
"@mainpage "+title+
"\\ilinebr ");
3738 else if (
id==
"mainpage" ||
id==
"index")
3740 if (title.
isEmpty()) title = titleFn;
3741 docs.
prepend(
"@ianchor{" + title +
"} " +
id +
"\\ilinebr ");
3742 docs.
prepend(
"@mainpage "+title+
"\\ilinebr ");
3744 else if (isSubdirDocs)
3748 docs.
prepend(
"@section " + generatedId +
" " + title +
"\\ilinebr ");
3750 docs.
prepend(
"@dir\\ilinebr ");
3761 docs.
prepend(
"@ianchor{" + title +
"} " +
id +
"\\ilinebr @ianchor{" + relFileName +
"} " + mdFileNameId +
"\\ilinebr ");
3763 else if (!generatedId.
isEmpty())
3765 docs.
prepend(
"@ianchor " + generatedId +
"\\ilinebr ");
3767 else if (
Config_getEnum(MARKDOWN_ID_STYLE)==MARKDOWN_ID_STYLE_t::GITHUB)
3770 docs.
prepend(
"@ianchor{" + title +
"} " + autoId +
"\\ilinebr ");
3772 docs.
prepend(
"@page "+
id+
" "+title+
"\\ilinebr ");
3774 for (
int i = 0; i < prepend; i++) docs.
prepend(
"\n");
3779 static const reg::Ex re(R
"([ ]*[\\@]page\s+(\a[\w-]*)(\s*[^\n]*)\n)");
3781 std::string s = docs.str();
3784 QCString orgLabel = match[1].str();
3785 QCString orgTitle = match[2].str();
3788 docs = docs.
left(match[1].position())+
3791 "\\ilinebr @ianchor{" + orgTitle +
"} "+orgLabel+
"\n"+
3803 p->commentScanner.enterFile(fileName,lineNr);
3805 bool needsEntry =
false;
3809 while (
p->commentScanner.parseCommentBlock(
3827 QCString docFile = current->docFile;
3828 root->moveToSubEntryAndRefresh(current);
3829 current->lang = SrcLangExt::Markdown;
3830 current->docFile = docFile;
3831 current->docLine = lineNr;
3836 root->moveToSubEntryAndKeep(current);
3838 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, fmt::format_string< Args... > fmt, Args &&... args)
static ParserManager * parserManager
static FileNameLinkedMap * imageNameLinkedMap
A model of a file symbol.
Minimal replacement for QFileInfo.
std::string fileName() const
std::string absFilePath() const
Helper class to process markdown formatted text.
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
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
size_t size() const
Returns the length of the string, not counting the 0-terminator.
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
Class representing a regular expression.
Object representing the matching results.
#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 isOtherPage(std::string_view data)
static bool hasLineBreak(std::string_view data)
@ explicitMainPage
docs start with a mainpage command
@ explicitPage
docs start with a page command
@ notExplicit
docs doesn't start with either page or mainpage
@ explicitOtherPage
docs start with a dir / defgroup / addtogroup command
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 an 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.
bool literal_at(const char *data, const char(&str)[N])
returns TRUE iff data points to a substring that matches string literal str
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
CommentScanner commentScanner
SrcLangExt getLanguageFromFileName(const QCString &fileName, SrcLangExt defLang)
QCString stripIndentation(const QCString &s, bool skipFirstLine)
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.